接口自动化测试框架
一、市面行情
1.主流接口测试工具实现接口自动化 (适用于中小型的公司或项目) 50%
Postman+Newman+git/Svn+Jenkins (基于Javascript语言)接口自动化Jmeter+Ant+Git/Svn+Jenkins (基于Java和BeanShell语言)接口自动化
2.基于代码的接口自动化 (适用于大中型公司或一线互联网公司) 40%
Python+Requests+Yaml+Pytest+Allure+Logging+热加载+Jenkins持续集成接口自动化
3.基于平台的接口自动化 (适用于于特大型或外包型公司) 30K以上,10%测试开发
二、全面认识Requests
requests模块式用于发送Http请求以及接口http响应的python的第三方库。安装pip install requests
详解requests: Alt+Enter自动导包
def get(url, params=None, **kwargs):
def post(url, data=None, json=None,**kwargs):
def put(url, data=None, **kwargs):
def delete(url,**kwargs):
def request(method, url,**kwargs):#这个是前面四个方法统一调用的方法
def session():#回话,web项目中从登陆和退出就是一个回话.session对象的request方法
def request(self, method, url, params=None,data=None,headers=None,cookies=None,files=Noneauth=None,timeout=None,allow redirects=True,proxies=Nonehooks=None,stream=None,verify=None,cert=None,json=None):
解析requests底层原理:
method,请求方式
url, 请求路径
params=None,get请求传参
data=None,post或put请求传参
json=None post请求传参
headers=None,请求头
cookies=None,Cookie信息
files=None,文件上传
auth=None,鉴权
timeout=None,超时处理
allow_redirects=True,是否允许重定向
proxies=None,代理
hooks=None,钩子
stream=None,文件下载
verify=None,证书验证
cert=None,CA证书
Response对象:
res .text 返回文本格式
res .content 返回bytes类型数据
res.json() 返回json数据
res .status_code 返回状态码
res .reason 返回状态信息
res.cookies 返回cookie信息
res.encoding 返回编码格式
res .headers 返回响应头
res .request.??? 返回请求的信息和数据
post请求中data传参和json传参的本质
Postman中Post请求的四种不同的传参方式以及它们对应的请求头
文件上传: Content-Type:multipart/form-data (files)
表单: Content-Type:application/x-www-form-urlencoded (data)
文本raw:
Content-Type:application/json (json)
Content-Type:text/plain (data)
Content-Typeapplication/javascript (data)
Content-Type:text/html (data)
Content-Type:application/xml (data)
二进制:
Content-Type:application/octrent-stream (files)
三、Requests接口自动化测试实战
接口关联三个层次:
1.通过类变量保存中间变量实现接口关联。
2.通过单独的文件保存中间变量实现接口关联。
3.极限封装成零代码的方式实现接口关联。
接口关联两种方式:
(1)正则提取实现接口关联
re.seach(‘name=”csrf_ token” value= “ ( .*?)” ,res. text).group(1) 通过正则匹配一个值, 通过下标[1]取值,没有匹配到返回None.
re.findall()通过正则匹配多个值,返回List, 通过下标取值,没有匹配到返回None
(2)JsonPath提取实现接口关联
jsonpath.jsonpath(res.json(),”$.access_token”) 返回一 个列表, 通过下标取值,没有找到返回None.
res.json()[‘access_token’]
requests .request()和session.request)的区别:前者的每个请求都是独立的,后者会自动的关联,所有请求的cookie信息。
四、接口自动化框架封装 (统一请求封装)
import requests
class Requestutil:
sess = requests.session()
#统一请求封装
def send_ all_request(self,**kwargs ):
res = RequestUtil.sess.request(**kwargs )
print(res.text)
return res
web项目的接口都会存在cookie关联。session对象能够自动的关联cookies
五、引入用例管理框架(单元测试框架)
python: unittest和pytest
java: junit和testing
unittest:ascii的大小来绝对的执行顺序,ord(‘a’):查看a的ascii码97;A 65
pytest默认从上到下的执行顺序
作用:
1.发现用例: 根据每个框架默认的规则去发现并且加载测试用例
2.执行用例: 将测试用例安装一定的顺序和条件执行,并生成结果
3.判断结果: 通过断言来判断实际结果和预期结果是否相同
4.生成报告:统计测试进度,耗时,通过率,生成报告
(2)优势
1.提高测试效率,降低维护成本
2.减少人工干预,提高测试的准确性,增加代码的重用性。
3.核心思想是让不懂代码的人也能够通过这个框架去实现自动化测试。
六、Pytest框架详解
pytest是一个非常成熟的python用例测试框架,它可以和很多的工具或框架,selenium,requests,appium实现多种自动化测试
它可以和allure结合生成美观的报告以及和jenkins实现持续集成最重要的是,它有很多的插件:
pytest-html #生成html报告插件
pytest-xdist #多线程
pytest-ordering #标记测试用例的执行顺序@pytest.mark.run (order=1)
pytest-returnfailers #失败用例重跑
pytest-base-url #管理基础路径
allure-pytest #生成allure报告
pytest
requests
pyyaml
使用requirements.txt文件保存,并通过以下命令执行包装:
pip install -r requirements.txt
遇到:pytest-returnfailers安装失败,需要到https://pypi.org/中搜索对应的版本下载安装即可
基于pytest单元测试框架,默认测试用例的规则
1.模块名(py文件)必须以test_ 开头或 _test结尾
2.测试类名必须Test开头,且不能有init方法
3.用例名必须以test 开头
七、执行的方式有三种
1.通过main执行
import pytest
if __name__ =='__main__':#入口
pytest.main(['-vs'])
(1)运行所有: pytest. main()
(2)指定模块: pytest. main([‘-vs’,’test_ login.py’])
(3)指定目录: pytest. main([‘-vs’,’./interface_ testcase’])
pytest. main([‘-vs’,’./interface_ testcase’,‘–html=report/report.html’,’–self-contained-html’])
(4)通过nodeid指定用例运行: nodeid由模块名,分隔符,类名,方法名,函数名组成。
pytest. main([‘-vs’,’./interface_ _testcase/test interface py::test 04 _func’])
pytest.main( [‘-vs‘,’/testcase’,’-n=2’])
2.通过命令行执行
pytest -vs
pytest -vs test_ login.py
pytest -vs ./interface_ _testcase/test interface.py::test 04 _func
执行的时候有一些参数:
-v:输出更详细的信息
-s:输出调试信息 (如打印)
-n:多线程运行测试用例
-q:保持输出简短
pytest -vs test_ login.py -n 2
-k:根据测试用例的部分字符串指定测试用例
pytest -vs test_ login.py -k “ao”
-x:表示只要有一个用例报错。那么测试停止
–maxfail=2出现两个用例失败就停止
–reruns 失败用例重跑
–html 生成HTML报告
从包运行测试
pytest --pyargs pkg.testing
读懂测试结果
可用字符的完整列表:
f
-失败E
-误差s
跳过x
-失败X
-XPASSp
通过P
-通过输出
修改python回溯打印
修改回溯打印的示例:
pytest –showlocals # 在回溯中显示局部变量
pytest -l # show local variables (shortcut)
pytest –tb=auto # (default) ‘long’ tracebacks for the first and last
entry, but ‘short’ style for the other entries
pytest –tb=long # exhaustive, informative traceback formatting
pytest –tb=short # shorter traceback format
pytest –tb=line # only one line per failure
pytest –tb=native # Python standard library formatting
pytest –tb=no # no traceback at all
分析测试执行持续时间
在 6.0 版更改.
要获取长度超过1.0秒的最慢10个测试持续时间的列表:
pytest –durations=10 –durations-min=1.0
3.通过读取pytest.ini配置文件来执行测试用例不管使用main还是命令行都会自动的去读取pytest.ini的配置文件
[pytest]
#命令行参数
addopts = -Vs
#改变默认的测试用例的路径
testpaths = ./testcase
#改变测试用例的模块名规则
python_files = test_*.py
#改变测试类的默认规则
python_classes = Test*
#改变测试用例名称规则
python_functions = test_*
#设置基础路径
base url = http://www.baidu.com
#标记执行:
markers =
smoke:冒烟测试
login:登陆用例
product_manage:商品管理
user_manage:用户管理
分组执行标记的案例
在用例上加@pytest.mark.smoke和@pytest.mark.product_manage
pytest -m smoke 运行所有用 @pytest.mark.smoke
装饰符
pytest -vs -m “smoke or product_manage” 两种装饰器都执行
pytest跳过测试用例
(1)无条件跳过
@pytest . mark. skip (reason=”微微”)
(2)有条件跳过
@pytest . mark. skipif (age>=18, reason=’已成年’ )
@unittest.skipUnless(age<18,”如果知了成年,忽略”)
def test03 zhiliao(self):
print (“测试知了”)
八、执行测试用例时的前后置处理
第一种:和unittest相似
模块级别: setup_module、teardown_module
函数级别:setup_function、teardown_function,不在类中的方法
类级别: setup_class、teardown_class
方法级别:setup_method、teardown_method,在类中的方法
用例级别:setup、teardown
def setup_class(self):
print("每个类之前执行")
def teardown_class(self):
print("每个类之后执行")
def setup(self) :
print("用例之前执行")
def teardown(self)
print("用例之后执行")
第二种: fixture固件,作用就是可以更加随心所欲的设置前后置
使用装饰器
@pytest.fixture(scope=””,params=””autouse=,ids=””,name=””)
scope:作用域
function(函数,用例,默认)
class 类
module 模块
package
session 会话
params:数据驱驱动。参数化(支持,列表,元祖(),字典列表[{},{},{}] ,字典元祖({},{},{})
autouse:自动作用还是手动作用
True自动调用,Fasle手动调用(需要在参数里面传入固件名称)
ids:当数据驱动时更改参数名
name:fixture的别名
案例:
@pytest.fixture(scope="function",autouse=False,params=read_yaml(),ids=['bl', 'bf' , 'xy'], name="ea" )
def exe_assert(request) :
print("在用例之前执行: 查询数据库用于断言")
yield request.param #生成器
print("在用例之后执行: 查询数据库”)
@pytest.fixture()
def driver():
driver = get_webdriver(#启动浏览器yield driver # 这是关键
driver.quit(#关闭浏览器
return和yield都表示返回的意思, 但是return的后面不能有代码,yield返回后后面可以接代码。
params=[‘成龙’,’甄子丹’,’菜10’] 这里params是参数名,有s。 request.param这里是属性名,是没有s的。
第三种:通过conftest.py和@pytest.fixture()结合使用实现全局的前后置处理(比如:项目的全局登录.模块的全局处理)
1.conftest py文件是单独存放的一个夹具配置文件,名称是不能更改。
2.用处可以在不同的py文件中使用同一个fxture函数。
3.conftest py需要和运行的用例放到统一层。 并且不需要做任何的imprt导入的操作。
封装更加合理的fixture
摆脱用例和用例直接的依赖
pytest.fixture(scope="module")
def driver():
"""未登录的浏览器"""
driver = get_webdriver()#启动浏览器
yield driver
driver.quit() #关闭浏览器
@pytest.fixture(scope="module")
def user_driver(): #己登录的浏览器
driver = get_webdriver()
driver.get(LoginPage.ur1)
page = LoginPage(driver)
page.1ogin("beifan_0609","123123")
page.wait_url_chenge() #等待登录完成,页面跳转
yield driver
driver.quit()
@pytest.fixture(scope="modu1e")
def c1ear_favor(user_driver):
#1.跳转到个人中心商品收藏页面
# 2.对页面进行操作,清除已有的收藏数据
user_driver.get(userGoodsFavorPage.ur1)
page = userGoodsFavorPage(user_driver)
page.delete_all()
九、YAML详解
yaml简介:yaml是一种数据类型,它可以和json之间灵活的切换,支持:注释,换行,字符串,裸字符串等。
用途: 1.配置文件 2.编写测试用例
安装:pip3 install PyYaml
数据结构:
1.Map对象(dict)(键:(空格)值) 如:name: 百里
2.数组(list),用一组横线”-“开头来表示 如:
yaml读取单个文档:yaml.safe_load()
- username: "test1"
- password: "111"
username1: "test2"
yaml读取多个文档:yaml.safe_load_all()
---
username: "test1"
password: "111"
---
username1: "test2"
password1: "123"
#用例
-
name: 获得token鉴权码的接口
request :
url: https://api.weixin.qq.com/cgi-bin/token
method: get
headers:
Content-Type: application/json
params:
grant_type: client_ credential
appid: wx6b11b3efd1cdc290
secret: 106a9c6157c4db5f 6029918738f9529d
validate :
- eq: { expires_in: 7200 }
import yaml
class YamlUtil :
def __init__ (self, yaml_file) :
"""
通过init方法把yam1文件传入到这个类
:param yaml_file:
"""
self.yaml_file = yaml_file
#读取yam1文件
def read yaml (self) :
"""
读取yam1,对yaml反序列化,就是把我们的yaml格式转换成dict格式。
"""
with open (self.yaml_file,encoding='utf-8',) as f:
value=yaml.load(f,Loader=yaml.FullLoader )
print (value)
return value
#写入:追加
def write_yaml(self,data):
with open(self.yaml_file,encoding="utf-8",mode="a+" ) as f:
yaml.dump(data, stream=f, allow_unicode=True)
I
#清空
def clear_yaml():
with open( self.yaml_file, encoding="utf-8" ,mode="w") as f:
f.truncate()
import os
import yaml
#1、创建类
class YamlReader:
#2、初始化,文件是否存在
def __init__(self,yamlf):
if os.path.exists(yamlf):
self.yamlf = yamlf
else:
raise FileNotFoundError("文件不存在")
self._ data = None
self._data_all=None
#3、yaml读取单个文档
def data(self):
#第一次调用data,读取yaml文档,如果不是,直接返回之前保存的数据
if not self._data:
with open(self.yamlf,"rb") as f:
self.data =yaml.safe_load(f)
return self._data
def data_all(self): #yaml读取单个文档
if not self._data_all:
with open(self.yamlf,"rb") as f:
self.data_all =yaml.safe_load_all(f)
return self._data_all
yaml配置文件设置
BASE:
test:
url: "http://211.103.136.242:8064"
import os
#1、获取项目基本目录
#获取当前项目的绝对路径
current =os.path.abspath(__file__)#print(current)
BASE_DIR = os.path.dirname(os.path.dirname(current))#print(BASE DIR)
#定义config目录的路径
config_path=BASE_DIR + os.sep +"config'
#定义conf.yml文件的路径
config_file =_config_path + os.sep +"conf.yml"
def get config path():
return config_path
def get config file():
return config_file
#2、读取配置文件#创建类
class ConfigYaml:
#初始yaml读取配置文件
def __init__(self):
self.config =YamlReader(get_config_file()).data()
#定义方法获取需要信息
def get_conf_url(self):
return self.config["BASE"]["test"]["url"]
十、Pytest用例管理框架数据驱动详解
python的反射
正常:先初始化对象,在调方法。
反射:通过对象得到类对象。然后通过类对象调用方法
数据驱动装饰器
@ pytest.mark.parametrize(ages1,args2)
args1:参数名: 用于传值。
vars2:参数值 (可以为列表或元祖,字典列表,字典元祖),数据中有多少个值那么用例就会执行多少次。
class TestLogin:
@pytest.mark.parametrize("caseinfo",["敏敏","北北","星星"])
def test_login(self,caseinfo):
print("登陆接口:“+caseinfo)
@pytest.mark.parametrize("name,age",[["敏敏","13"],["北北","12"],["星星","11"]]) #第二种方式
def test_register(self,name, age):
print("注册接口: %s"% name,age)
第二种方式:跟unittest的ddt里面的@unpack解包的一样
excel工具类
import xlrd
import os
#目的:参数化,pytest list
#自定义异常
class SheetTypeError:
pass
#1、验证文件是否存在,存在读取,不存在报错
class ExcelReader:
def __init__(self,excel_file,sheet_by):
if os.path.exists(excel_file):
self.excel_file = excel_file
self.sheet_by = sheet_by
self._data=list()
else:
raise FileNotFoundError("文件不存在")
#2、读取sheet方式,名称,索引
def data(self):
#存在数据不读取,反之
if not self._data:
workbook =xlrd.open_workbook(self.excel_file)
if type(self.sheet_by) not in [str,int]:
raise SheetTypeError("请输入Int or Str")
elif type(self.sheet_by)== int:
sheet = workbook.sheet_by_index(self.sheet by)
elif type(self.sheet_by)==str:
sheet=workbook.sheet_by_name(self.sheet_by)
#3、读取sheet内容,list内嵌字典
#1.获取首行的信息
title =sheet.row_values(0)
#1 循环,过滤首行,从1开始
for col in range(1,sheet.nrows):
col_value= sheet.row_values(col)
#2 与首组成字典,放list
self._data.append(dict(zip(title, col_value)))
return self._data
十一、pytest结合allure生成美观的报告
1.下载allure,下载之后解压,解压之后配置path环境变量.需要配置jdk,jre
使用pycharm配置JDK 或者]RE
D: \Program Files \JetBrains Pycharm Community Edition 2019.2.2 \jbr
将目录添加到环境变量JAVA HOME
path路径的配置: D:\allure-2.13.7\bin (路径中不能有中文)
验证: allure –version (需要在dos和pycharm中都要验证)如果说Pycharm中验证失败,那么需要重启pycharm,让环境生效。
feature:一级标签,对应excel的sheet名称
story:二级标签,模块
title:用例ID+接口名称
description:请求URL 请求类型 期望结枼 实际结果
Severity:定义用例的级别,主要有BLOCKER,CRITICAL,MINOR,NORMAL,TRIVIAL等几种类型
,默认是NORMAL
@allure.title("测试用例标题2”)
@allure.description("执行测试用例1的结果是test2")
@allure.story("这是一个二级标签:test1')
@allure.severity(allure.severity_level.CRITICAL)
def test(self):
print("test2")
Allure.dynamic:动态设置相关配置
#写在用例里面
#sheet名称 feature 一级标签
allure.dynamic.feature(sheet name)
#模块 story 二级标签
allure.dynamic.story(case model)
#用例ID+接口名称 title
allure.dynamic.title(case id+case name)
#请求URL 请求类型 期望结果 实际结果描述
desc ="<font color="red">请求URL:</font>{} <Br/> \
<font color="red">请求类型:</font>{} <Br/> \
<font color="red">期望结果</font>{} <Br/> \
<font color="red">实际结果</font>{}".format(url,method,expect_result,res)
allure.dynamic.description(desc)
2.在pytest.ini文件中加入命令
[pytest]
addopts = -vs --alluredir=./temps --clean-alluredir
#执行目录
testpaths = testcase
#执行目录下的文件
python_files = test_*.py
#执行测试类#
python_classes = Test_*
#执行测试方法
python_functions =test_*
3.生成Allure报告
os.system("allure serve .allure_results")
在cmd窗口中执行
allure generate .temps -o report --clean
allure generate 命令,固定的
./temp 临时的json格式报告的路径
-o 输出output
./report 生成的allure报告的路径
–clean 清空./report 路径原来的报告
查看报告
必须使用HTTP协议对报告[目录]进行访问
应该是url
日志
在pytest.ini文件中添加 log_file_level = info
在每个页面类中加入
loggers = logging.getLogger( __name__)
在测试用例的开始或结束加上logger.info(“打印信息”)
截图
在用户用例的执行前后截图加入到allure报告中
allure.attach(
# 交互后进行截图,确认交互效果
se1f._driver.get_screenshot_as_png(),
)
Subprocess介绍
允许你产生新的进程,并且可以把输入,输出,错误直接连接到管道,最后获取结果
官方网站:>https://docs.python.org/2/library/subprocess.html
方法:>subprocess.call()
父进程等待子进程完成,返回退出信息(returncode,相当于Linux exit code)
shell=True 的用法,支持命令以字符串的形式传入。
def allure_report(report_path,repart_html)
#执行命令 allure generate
allure_cmd ="allure generate %s -o %s --clean"%(report_path,repart_html)
log.info("报告地址”)
try:
subprocess.call(allure_cmd,shell=True)
except:
log.error("执行用例失败,请检查一下测试环境相关配置")
raise
十二、Web端自动化测试:
1.极高的效率执行
2.完成复杂的业务场景
3.记录和调试网页的细节
目前主流的工具:
Cypress
Playwright
Selenium
1.Selenium自动化环境搭建
pip insta11 webdriver_helper-0.0.15-py3-none-any.whl
from webdriver_helper import get_webdriver
driver = get_webdriver("ie") # 支持chrome、firefox 、ie
driver.get("https://baidu.com")# 控制浏览器
driver.quit() # 退出浏览器
只支持Python3.9及以上版本
2.浏览器交互
selenium 以面向对象的思路,完成自动化
获取数据: 访问对象的属性
操作数据: 调用对象的方法
所以: 对浏览器的控制,对driver对象的方法进行调用
maximize_window() 最大化窗口
get(url) 跳转指定页面
refresh() 刷新
back() 后退
forward() 前进
get_screenshot_as_png() 截图的base64内容
get_screenshot_as_file(path) 截图保存到文件
current_url 网址
page_source 源码
current_window_handle 当前窗口
window_handles 所有窗口
switch_to.alert 处理弹窗
switch_to.frame(‘frame_name’) 切换框架
switch_to.window(‘window_name’) 切换窗口
from webdriver_he1per import get_webdriver
driver = get_webdriver()# 支持chrome、firefox . ie
driver.get_screenshot_as_file("1_启动.png")#文件名字必须png结尾
driver.maximize_window()
driver.get_screenshot_as_file("2.最大化.png")
driver. get("https: // baidu. com")#控制浏览器
driver.get_screenshot_as_file("3.访问baidu. png")
print("当前的网址: ",driver.current_ur1)
print("当前的标题:",driver.title)
print("当前的HTML: ",driver. page_source)
driver.quit()#退出浏览器
driver.switch_to.frame(id)
driver.switch_to.default_content() #
data=driver.window_handles #[s001,s002]
driver.switch_to.window(data[0])
ac = ActionChains(driver)
#手动获取滑块
ele = driver.find_element(By.XPATH,’//*[@id=”box”]/div[3]’)
ac.click_and_hold(ele).move_by_offset(300,0).perform()
ac.release()
3.元素定位
浏览器已经被webdriver-helper启动好了,可以直接进行交互元素需要被定位到,才能对其进行交互
Selenium提供8个定位策略
定位器 描述
id 定位id属性与搜索值匹配的元素
name 定位name属性与搜索值匹配的元素
tag_name 定位标签名称与搜索值匹配的元素
class_name 定位class属性与搜索值匹配的元素(不允许使用复合类名)
link_text 定位link_text可视文本与搜索值完全匹配的锚元素
partial_link_text 定位link_text可视文本部分与搜索值部分匹配的锚点元素
xpath 定位与XPath表达式匹配的元素
css_selector 定位CSS 选择器匹配的元素
1.根据属性进行定位
元素定位的时候:满足定位条件的元素,可能不只一个
<input
id="kw" #只有ID属性,有唯一性的约束
name="wd"
class="s_ipt"
value=""
max1ength="255"
autocomplete="off"
>
driver = get_webdriver( #支持chrome、firefox . ie
driver.get("https://baidu.com")#控制浏览器
e1_1 = driver.find_element(By.TAG_NAME,"input")
e1_2 = driver.find_element(By.ID,"kw")
e1_3 = driver.find_element(By.NAME,"wd")
e1_4 = driver.find_element(By.CLASS_NAME,"s_ipt")
print(e1_1)
print(e1_2)
print(e1_3)
print(el_4)
e1_5 = driver.find_elements(By.TAG_NAME,"input")[7]
print(e1_5)
driver.quit()#退出浏览器
2.根据文本进行定位
只能定位a元素
<a
href="http: //news.baidu . com"target="_b1ank"
c1ass="mnav c-font-normal c-color-t">
新闻
</a>
driver.get("https://baidu.com") #控制浏览器
el_1 = driver.find_element(By.LINK_TEXT,“新闻")#精确匹配
el_2 = driver.find_element(By.PARTIAL_LINK_TEXT,"新")#模糊匹配
print(e1_1)
print(e1_2)
3.根据表达式进行定位
driver = get_webdriver() #支持chrome、firefox . ie
driver.get("https://baidu.com")#控制浏览器
el_1 = driver.find_element(By.css_SELECTOR,"#kw")#精确匹配
el_2 = driver.find_element(By.XPATH,'//*[@id="kw"]')#模糊匹配
print(e1_1)
print(e1_2)
driver.quit() #退出浏览器
CSS和XPath 怎么选:
如果有前端开发配合,或自己就是前端开发:选择CSS
如果没有前端开发配合:选择XPath
支持层级跳转
支持布尔值,函数
5.XPath语法
//*[@id="kw" and @name="beifan"]
1.XPath基本语法
层级
属性
/ :(开头) 根路径 /html/body
//:(开头、中间) 任意层级 //*[@id=”kw”]
*:任意元素
@: 属性筛选
/: (中间)下一级 /html/body
.: 本级
.. :上一级/
(模糊匹配)
starts-with开头相同
(半模糊匹配)
3.XPath 调试
开发者工具—控制台—- $x(‘路径’)进行XPath调试
XPath 写出来之后,先在F12验证一下,然后放入测试代码
跳转指定网址:window.location.href=‘www.baidu.com’
模拟弹窗
开发者工具—控制台—-alert(“123”)
确认弹窗:confirm(“123”) 输入弹窗:prompt(“123”)
处理一闪而过的窗口
打开console,输入代码setTimeout(() => {debugger;}, 4000); 回车—->表示4秒后冻结窗口,除非手动结束,否则不会消失,这时候4秒内点击登录,弹出消息就被冻结了
开发者工具—控制台——–debugger暂定后选元素定位
解决id变化递增的问题
只能自己写xpath了
el = driver.find_element(By.XPATH,
'//*[starts-with(@id, "layui-layer")]/div[2]')
print(el.text)
4.元素交互
面向对象: 属性 方法
属性 内容 例子
id 唯一标记 36f-6a18
tag_name 标签名 input
location 元素坐标 {‘x’: 298, ‘y’: 188}
size 元素大小 {‘height’: 44, ‘width’: 548}
rect 元素范围 {‘height’: 44, ‘width’: 548, ‘x’: 298, ‘y’: 188}
parent WebDriver实例 略
screenshot_as_base64() 截图的base64内容 iVBORw0KGgoAAAAN….
screenshot_as_png() 截图的二进制内容 b’\x89PNG\r\n\x1a\n\x…
get_attribute(name) 获取元素的HTML属 性 value_of_css_property 获取CSS属性
click() 点击
clear() 清空内容
send_keys(content) 输入内容
三大等待
1.强制等待time.sleep(2)
2.隐式等待driver.implicitly_wait(10)# 大于0 的参数,表示启用隐式等待
3.显式等待自定义等待的时机 WebDriverWait(driver,5)
ele = webDriverWait(driver,10).until(
lambda _: driver.find_element(
By.XPATH,"/htm1/body/div[4]/div/u1/1i[3]/div/a/div/p")
)
5.POM
1.写出元素定位表达式 2. 写出合适等待方式 3. 维护各个元素的定位表达
Page Objects Model 页面对象模型
类 表示 页面
类属性 表示 页面中的元素
类的方法 表示 对页面的操作
封装po
定义过程:
1.定义 BasePage(抽象类)
2.定义Page(页面类)
3.定义元素的交互(对象方法)
使用过程:
1.打开页面,实例化Page
2.调用Page方法,完成交互
使用po
driver = get_webdriver(driver.get(LoginPage.ur1)
page = LoginPage(driver)#实例化PO
page.login("beifan_0609","123123")#登录msg = page.get_msg(#获取系统提示
assert msg =="登录成功"
driver.get(GoodsPage.ur1)
page = GoodsPage(driver)msg = page.add_to_cartO
assert msg =="加入成功"
driver.quit()
框架运行流程
1.在excel列出用户动作 (关键字)
⒉.框架读取、执行关键字
3.加载其他插件,实现并行测试、失败重试、HTML报告
Sanmu是一个纯用Excel描述测试用例的UT自动化测试框
默认执行run_pytest命令
options:
–help Show this message and exit.
Commands :
report 调用allure,生成HTML报告
run-pytest 启动Pytest框架执行用例,通过pytest-html生成报告
run-unittest 启动UnitTess框架执行用例,通HTMLTestRunner生成报告
show-keys 打印关键字文档
start 创建demo文件,并命名为test_{name}.xlsx
openpyxl + execl 数据驱动测试
新的案例实战:添加收获地址
1.编写测试用例
def test_user_new_address(user_driver):
user_driver.get(UserAddressPage.url)
page = UserAddressPage(user_driver)
page = page.new_address() # 点击新增地址 按钮
page_msg = page.submit(
"北凡", "13191121211", "湖南省", "长沙市", "岳麓区", "桐梓坡路117号"
) # 输入数据 +提交表单
assert page_msg == "操作成功"
2.加载execl测试数据
pip install openpyxl
def read_excel(filename):
path = data_dir / filename
wb = load_workbook(path)
ws = wb.active
yield from ws.iter_rows(min_row=2, values_only=True)
3.数据驱动测试
@pytest.mark.parametrize(
"name,tel,province,city,county,address,alias,is_default,msg",
read_excel("ddt_test_new_address.xlsx"),
)
def test_user_new_address(
user_driver,
name,
tel,
province,
city,
county,
address,
alias,
is_default,
msg,
):
user_driver.get(UserAddressPage.url)
page = UserAddressPage(user_driver)
page = page.new_address() # 点击新增地址 按钮
page_msg = page.submit(
name,
tel,
province,
city,
county,
address,
alias,
) # 输入数据 +提交表单
assert page_msg == msg
十三、Jenkins以及持续集成简介
jenkins是一个可以扩展的持续集成和持续部署的平台。它也只是一个平台,主要运行的都是插件
持续集成:把整个软件生命周期中的所有工作实现自动化,以流水线的方式去完成软件的研发过程
开发:编写代码并且进行源码管理,编译打包提供给测试人员测试
测试:部警测试环境进行功能测试,持续集成自动化测试
运维:部署线上环境
Jenkins的安装
1.安装JDK以及配置JDK的环境变量
2.插件的下载(1-2小时的下载 )
3访问:http//localhost:8080
Jenkins创建以及使用job(项目)应用
1新建job
2目定义工作空间:指定你的项目在哪里
3.输入执行命令:python all.py
4执行groovy脚本:System.setProperty(“hudson.model.DirectoryBrowserSupport.CSP”,””)
Jenkins集成Allure报告
1.安装allure插件: Allure Jenkins Plugin
2.在[全局工具配置Global Tool Configuration],配署Allure
3.在job配置allure报告
Jenkins持续集成企业微信(钉钉)实战
1创建企微群管理机器人,获得webhook
webhook
https//avapi.weixinggcom/cgi-bin/webhook/send?kev=bed4f5d11e1-4694-96a4-6f3be8a17197
2安装插件:Qv wechat…
3.在[系统配置]添加默认webhook地址
4.进入job,添加构建后操作。配置企微通知
Jenkins集成电子邮件通知实战
1.安装插件: Email…
2.新建一个邮箱。建议用163,企业邮箱肯定可以。
设置打开POP3(接受邮件的协议)和SMTP(发邮件的协议),获取的客户端的密码:bfwrrjqrzhxldgea
163 : BKFUMLHFHQBYGUPV
3.在[系统配置]配置邮件的发件人,SMTP服务器,收件人信息
4.进入job,添加构建后操作。配置邮件通知
系统管理员邮件地址必须和发件人保持一致
class EmaiManage :
def send email(self,report_name):
#定义SMTP服务器
smtpserver =smtp.163.com
#发送邮件的用户名和客户端密码
username='mashang_XXX@163.com'
#授权密码password =KNLOCNORUHWHIXJQ
#接收邮件的邮箱
receiver =XXX@gq.com
#创建邮件对象
message = MIMEMultipart('related')
subject ='湖南一网通项目自动化测试报告' #邮件的主题
fujian = MIMEText (open(report_name,'rb').read(),"html','utf-8') #附件
#把邮件的信息组装到邮件对象里面
message['form'] = username
message['to'] = receiver
message['subject'] = subject
message.attach(fujian)
#登录smtp服务器并发送邮件
smtp = smtplib.SMTP()
smtp.connect(smtpserver)
smtp.login(username,password)
smtp.sendmail(username,receiver,message.as_string())
smtp.quit()
十四、Unittest框架
1.用例编写规则
unittest(python自带): 提供了testcases测试用例、testsuite测试套件、testfixtures测试件或夹具、testloader测试加载器,testrunner测试运行器。必须遵守以下规则:
(1)测试文件必须先导入import unittest
(2)测试类必须继承unittest.TestCase
(3)测试方法必须以test开头。
pytest:它是python的第三方测试框架。基于unittest的扩展框架,必须遵守以下规则:
(1)测试文件名必须以test 开头或者是 test结尾
(2)测试类命令必须以Test开头
(3)测试方法必须test开头
2.用例的前后置
setUp/tearDown 在每个用例之前或之后与运行一次
例如:打开浏览器,加载网页/关闭网页
setUpClass和tearDownClass 在每个类运行之前或之后运行一次
class Test(uittest.TestCase):
@classmethod #在那类中需要加标注
def setUpClass(cls) -> None:
print("setupclass:在每个类之前执行一次。如: 创建数据库,生成日志对象”)
例如:创建数据库连接,创建日志对象/关闭数据库连接,销毁日志对象
setUpModule和tearDownModule 在每个模块之前和之后执行一次
3.断言
unittest : assertTrue(a)/assertFalse(a) , assertEqual(a,b)/assertNotEqual(a,b) , assertln(a,b):断言a在b中
pytest : assert
4.报告
unittest : htmltestrunner
pytest : 插件 : pytest-HTML ,allure
5.失败重跑
unittest:没有
pytest: pytest-rerunfailures插件
6.数据驱动
unitest : ddt
pytest: @pytest.markparametrize装饰器
7.用例分类执行
unittest: 默认执行所有,也可以通过testsuite来执行部分用例,或者-k参数
pytest : @pytest.mark
TestCase用法
import unittest
class EcshopLogin(unittest.TestCase):
#测试用例
def test01_baili(self):
print(”测试用例")
1.使用unittest命令行的方式运行python -m unittest -v ecshop_login.EcshopLogin
python-m 以脚本的方式去运行一个模块
unittest-v–verbose 意思就是更详细的输出结果
ecshop_login.EcshopLogin 为模块名.类名.方法名
2.使用unittest.main(),以模块的方式运行
配置pycharm的运行环境或者在命令行使用:python 模块名.py
读懂测试结果
结果缩写符号符号 含义
. 通过
F 失败
E 出错
X XPass意外通过
x xfailed 预期失败
S 跳过
x表示反义词
如果通过,什么都不显示
如果失败,则显示代码和报错信息
通过参数可以让结果更加的丰富
unittest.main()底层参数
module=’__main__’,测试用例用例所在的路径main 表示当前模块
defaultTest=None, 待测用例的名称,默认是所有
argv=None, 接收外部的参数
testRunner=None, 测试运行器,TextTestRunner
testLoader=loader.defaultTestLoader, 指定使用默认的测试用例加载器
exit=True 是否在测试完成之后结束python程序
verbosity=1 类似于命令行-V,0,1,2
只运行部分用例使用testCase
suite = unittest.Testsuite()
suite.addTest(EcshopLogin("test01_baili"))
suite.addTest(EcshopLogin("test02_weiwei"))
unittest.main(defaultTest=' suite') #本质上是和下面的命令一样效果的
#unittest.TextTestRunner().run(suite)
------------------------------
suite = unittest.TestSuite()
testcases = unittest.defaultTestLoader.discover(start_dir=osgetcwd(),pattern="*.py")
suite.addTests(testcases)
unittest.main(defaultTest='suite')
批量执行用例生成HTML格式的报告
在网上下载HTMLTestRunner.py文件放到python安装路径下:C:\python373\Lib
数据驱动ddt:DDT:Data Driver Tests
ddt 是第三方模块,需安装:
pip install ddt
DDT包含类的装饰器ddt和方法装饰器data
通常情况下,data中的数据按照一个参数传递给测试用例,如果data中含有多个数据,如元组,列表,字典等数据,需要自行在脚本中对数据进行分解或者使用unpack分解数据。
@ddt 类装饰器,用来标记当前类使用ddt数据驱动
@data 函数装饰器,用来给函数传递数据
@unpack 函数装饰器,用来对传递的数据进行解包,解列表、元组、字典
@file_data 函数装饰器,用来直接读取yaml,json格式的文件数据 读取获取的数据是字典列表
案例:直接在脚本文件中获取测试数据
@data(a,b)
那么a和b各运行一次用例
@data([a,b],[c,d])
如果没有@unpack,那么[a,b]当成一个参数传入用例运行
如果有@unpack,那么[a,b]被分解开,按照用例中的两个参数传递
import unittest
from ddt import ddt, data, unpack
case_data = [{"url": "www.baidu.com", "data": "test1"},
{"url": "www.baidu.com", "data": "test2"},
{"url": "www.baidu.com", "data": "test3"}]
@ddt
class TestDemo(unittest.TestCase):
@data(*case_data) #在测试函数上使用@data(*case_data)进行数据解压,然后每一个元素传入测试函数
def test_case(self, case):
print(case)
运行结果:
Ran 3 tests in 0.005s
OK
{‘url’: ‘www.baidu.com’, ‘data’: ‘test1’}
{‘url’: ‘www.baidu.com’, ‘data’: ‘test2’}
{‘url’: ‘www.baidu.com’, ‘data’: ‘test3’}
import unittest
from ddt import ddt, data, unpack
@ddt
class MyTesting(unittest.TestCase):
@data([1, 2, 3]) #没有@unpack,那么当成一个参数[1, 2, 3]
def test_1(self, value):
print(f'test_1:value={value}')
@data(4,5,6) #三个元素4,5,6
def test_2(self, a):
print(f'test_2:a={a}')
@data([3, 2, 1], [5, 3, 2], [10, 4, 6])
@unpack #有@unpack,那么分解成3个参数传入用例运行:3, 2, 1是3个参数
def test_minus(self, a, b, expected):
actual = int(a) - int(b)
expected = int(expected)
self.assertEqual(actual, expected)
@data([2, 3], [4, 5]) #没有@unpack,那么当成一个参数:[2, 3]是一个参数,[4, 5]是一个参数
def test_compare(self, a):
print(f'test_compare:a={a}')
if __name__ == '__main__':
unittest.main(verbosity=2)
结果如下:
OK
test_1:value=[1,2,3]
test_2:a=4
test_2:a=5
test_2:a=6
test_compare:a=[2,3]
test_compare:a=[4,5]
yml文件中获取测试数据
在testdata package下新建testdata1.yml文件,文件中写入测试数据如下:
#格式要求:
#"-"表示一个列表
#用tab键缩进
-
name: 'testyy'
age: 18
-
name: 'testzz'
age: 20
test_ddt.py文件中修改测试代码如下:
import unittest
import ddt # 导入ddt
import yaml
@ddt.ddt # 声明我们要用它
class Test_ddt(unittest.TestCase):
def setUp(self):
print("Before every test case!")
@ddt.file_data("..\\testdata\\testdata1.yml") #括号里写yml文件的相对路径
def test_002(self,name,age):
print('name is:' +str(name))
print('age is:' + str(age))
if __name__ == '__main__':
unittest.main()
yml文件中我们定义了2组数据,用例总共执行了2次,按顺序每次取一组数据作为入参。
从json文件中获取测试数据
在testdata package下新建testdata2.json文件,文件中写入测试数据如下:
{
"student1": {
"name": "小明",
"age": 12
},
"student2": {
"name": "小张",
"age": 20
}
}
test_ddt.py文件中修改测试代码如下:
import unittest
import ddt # 导入ddt
import yaml
@ddt.ddt # 声明我们要用它
class Test_ddt(unittest.TestCase):
def setUp(self):
print("Before every test case!")
@ddt.file_data("..\\testdata\\testdata2.json")#括号里写json文件的相对路径
def test_003(self,name,age):
print("name is:"+str(name))
print("age is:" +str(age))
if __name__ == '__main__':
unittest.main()
十五、RobotFramework的简介和特点
简介:RF是一个基于Python语言开放的,可扩展的,以关键字驱动模式的自动化测试框架。
关键字驱动:关键字驱动表示把项目中的业务逻辑封装成一个一个的关键字,然后调用不同的关键字组成不同的业务。
数据驱动:数据驱动是把测试数据放到excel,jaml文件里面,然后通过改变文件中的数据去驱动测试用例执行。
特点:
1.编写用例非常方便,常见的有txt,robot
2.自动化生成html报告
3.非常强大的扩展库。
4.根据项目的需求自定义关键字
5.支持非GUI的方式运行,以及和jenkins实现持续集成
安装注意事项
1.安装python3.7以及环境变量
2安装robotframework
注意:最新的版本是4.1.2
安装的是3.1的版本pip install robotframework==3.1
3.安装RIDE开发工具:
命令 : pip install robotframework-ride
会弹出一个窗口,点确定,在卓面生成一个RIDE快捷方式。
豆辨下载源:
pip install i https://pypi.douban.com/simple robotframework==3.12
pip install i https://pypi.douban.com/simple robotframework-ride
查看:pip list
使用RIDE开发工具
1.new project 注意: 选择Directory
2创建文件夫
3.创建测试套件
4.创建测试用例
5.创建资源文件(在文件夹下面创建资源文件,格式必须选择txt格式]。一个资源文件下面可以创建很多个用户目定义的关键字,资源文件可以在测试套件中导入并调用它下面的自定义关键字.
Robotframework常用的库
1.标准库(rf自带的库)
Builtin(测试库)
Collections(集合库)
DateTime(时间库)
Screenshot( 截屏库)
标准库保存的位置:C:\python373\Liblsite-packages\robotlibraries
2.扩展库(通过pip命令安装的库)
Web自动化测试:SeleniumLibrary Selenium2Library
安装: pip install roboframework-seleniumlibrary
接口自动化测试: RequestsLibrary
安装: pip install roboframework-requests
APP自动化测试:AppiumLibrary
安装: pip install roboframework-appiumlibrary
扩展库保存的位置:C:\python373\Libisite-packages
常用关键字
快捷键:
F5 搜索关键字
F8 运行测试用例
Ctrl+Shfit+空格 自动补全关键字
Log | 呵呵 | |||
---|---|---|---|---|
Comment | log | 老师 | ||
${a} | Set Variable | 100 | ||
log | ${a} | |||
Sleep | 3 | |||
${times} | Get Time | |||
Log | ${times} | |||
${content_str} | Catenate | SEPARATOR=# | 大道 | 实施 |
log | ${content_str} |
结果:
呵呵
s(a)= 100
100
s{times} = 2023-10-14 20:59:30
2023-10-14 20:59:30
${content_str)=大道#实施
大道#实施
复杂关键字
${list1} | Create List | 百里 | 北凡 | 星瑶 |
---|---|---|---|---|
log | ${list1} | |||
@{list1} | Create List | 百里 | 北凡 | 星瑶 |
Log Many | @{list1} | |||
${dic} | Create Dictionary | name=北里 | age=18 | |
log | ${dic} | |||
${keys} | Get Dictionary Keys | ${dic} | ||
log | ${keys} | |||
${values} | Get Dictionary | ${dic} | ||
log | ${values} | |||
${value_to_name} | Get From Dictionary | ${dic} | age | |
Comment | 直接执行python函数 | |||
${random_number} | Evaluate | random.randint(0,100) | modules=random | |
log | ${random_number} | |||
${times} | Evaluate | time.time() | modules=time | |
log | ${times} | |||
Comment | 直接调用python自定义方法 | |||
Import Library | E:/test.py | |||
${a} | Evaluate | int(10) | ||
${b} | Evaluate | int(20) | ||
${he} | sum | ${a} | ${b} | |
log | ${he} |
注意:对于没有 get dictionary keys方法,需要在Edit—>Library—>导入Collections
结果:
${list1} =[‘百里‘,‘北凡‘,‘星瑶]
[‘百里‘,‘北凡‘,‘星瑶]
@{list2}=[百里 | 北凡 | 星瑶 ]
百里
北凡
星瑶
${dic}={‘name’:’百里‘,’age’:18}
{‘name’:’百里‘,’age’:18}
${keys}=[‘age’,’name ‘]
[‘age’,’name ‘]
${values}=[‘18’,’百里’]
[‘18’,’百里’]
${value_to_name}=18
18
${random_number} = 40
40
${times} =1637759780.143498
1637759780.143498
${a}=10
${b}=20
${he}=30
30
${a} | Set Variable | 20 | |||
---|---|---|---|---|---|
Run Keyword If | ${a}>=80 | log | 成绩优秀 | ||
… | ElSE IF | ${a}>=60 | log | 成绩及格 | |
… | ELSE | ifs | ${a} | ||
FOR | ${a} | IN | 等等 | 查查 | 信息 |
log | ${a} | ||||
END | |||||
FOR | ${a} | IN RANGE | 1 | 11 | 2 |
RUN Keyword If | ${a}==8 | Exit For Loop | |||
log | ${a} | ||||
@{list1} | Create List | 百里 | 北凡 | 星瑶 | |
FOR | ${a} | IN | @{list1} | ||
log | ${a} | ||||
END |
ifs为业务关键字文件中的if-else结构
断言AssertUtil
#1、定义封装类
class AssertUtil:
#2、初始化数据,日志
def __init__(self):
#self.log = my_log("AssertUtil")
pass
#3、code相等
def assert_code(self,code,expected_code):
try:
assert int(code)== int(expected_code)
return True
except:
print("code error,code is %s,expected code is %s"%(code,expected_code))
#4、body相等
def assert_body(self,body,expected_body):
#验证返回结果内容相等
try:assert body == expected_body
return True
except:
print("body error,body is &s,expected body is %s"%(body,expected_body))
#5、body包含
def assert_in_body(self,body,expected_body):
#验证返回结果是否包含期望的结果
try:
body = json.dumps(body)
assert expected_body in body
return True
except:
print("不包含或者body是错误,body is %s,expected body is %s"%(body,expected_body))
数据库mysqlutil
import pymysql
class Mysql:
#2、初始化数据,
def __init__(self,host,user,password,database,charset="utf8",port=3306):
self.conn = pymysql.connect(host=host,user=user,password=password,
database=database,charset=charset,port=port)
self.cursor =self.conn.cursor()
#创建查询、执行方法
def fetchone(self,sgl):
self.cursor.execute(sql)
return self.cursor.fetchone()
def fetchall(self,sql):
self.cursor.execute(sql)
return self.cursor.fetchall()
def exec(self): #update,delete,insert
try:
if self.conn and self.cursor:
self.cursor.execute(sql)
self.conn.commit()
except Exception as ex:
self.conn.rollback()
print("执行异常")
return False
return True
def __del__(self):
if self.cursor is not None:
self.cursor.close()
if self.conn is not None:
self.cursor.close()
python接口数据关联
class Context:
#这是一个承接上下文的数据
前一个接口
使用setattr对其它接口需要的数据进行传递,加载到内存中去,比如说后一个接口需要 token
setattr(Context,"token",req.json()["data"]["token"])
后一个接口,使用getattr
getattr(Context,"tocken")
jemeter跨线程组实现接口关联
前提:第一个接口和第二个接口存在接口关联(通过token鉴权),同时还存在cookie鉴权。
接口压测:接口压力测试的任务,要求对其中一个接口进行500虚拟用户并发。但是这个接口必须要先 登陆之后获取到token才可以请求成功!
1.首先设置jmeter.propties里面的参数
CookieManager.save.cookies=true
才能够看到cookie
COOKIE_W2S_lastvisit=0%091652876410%09%2Fphpwind%2F
COOKIE_W2S_visitor=oF9u%2F8%2BTDp8OtNSRBld43kbit5MjxSfalRd7bm9lHA7Fy173WZoO1g%3D%3D
COOKIE_csrf_token=23f8b0cf8d839d02
2.跨线程组cookie鉴权: 只能在一个线程组里面传值的叫局部变量(正则,JSON,用户自定义变量) 能够在多个线程组之间传值的叫全局变量
需要通过Beanshell组件(既能使用java语言,也有自己的语法的组件)来设置:
__setPropty() 设置Jmeter的全局属性。能够在多个线程组之间传值
__P() 获取全局属性
埋点:
在测试报告的生成过程中埋点,拿到数据通过数据库操作存储到数据库
在conftest.py中,加入团队和负责人数据
impot pytest
@pytest.hookimpl(hookwrapper=True,tryfirst=True)
def pytest_runtest_makereport(item, call):
#是通过yeild做轮询
out = yield
report = out.get_result()
if report.when == "call": #固定过滤语句
print(f"测试报告:{report}")
print(f"步骤:{report.when}")
print(f"node id:{report.nodeid}")
print(f"执行结果:{report.outcome}")
#下面的if 是获取我们 info 标签中注册的内容:比如我在用例中注册的是 team principal
if "pytestmark" in item.function.__dict__:
for i in item.function.pytestmark:
if "info" in i.__dict__.values():
values =(report.location[0],report.location[-1],item.originalname,report.outcome,i.__dict__["kwargs"]["team"], i.__dict__["kwargs"]["principal"])
#存储到数据库
mysql.change(f"""insert into test_cases(caseId,
,testId, test_name, result,team,principal) values {values};""")
else:
values =(report.location[0],report.location[-1],item.originalname,report.outcome)
mysql.change(f"""insert into test_cases(caseId,
,testId, test_name, result) values {values};""")
else:
values =(report.location[0],report.location[-1],item.originalname,report.outcome)
mysql.change(f"""insert into test_cases(caseId,
,testId, test_name, result) values {values};""")
使我们的测试用例携带其他维度的数据
在多个测试用例方法上加入
@pytest.mark.info(team=”第一团队”,principal=”长风”)
附录:接口自动化测试框架
文件夹加说明:
commons: 公共封装文件夹
datas: YAML数据驱动文件夹
hotloads:热加载文件夹
logs:日志文件夹
reports:定制的allure报告文件夹
temps:临时报告文件夹
testcases:YAML测试用例文件夹
config.yaml:全局配置文件
conftest.py:全局fixture固件
extract.yaml:全局接口关联中间变量提取文件
pytest.ini:全局pytest配置文件
run.py:全局运行文件
readme:接口自动化测试框架说明文件
接口自动化测试框架YAML测试用例规范:
-
feature: 用户相关
story: 登陆b2c商城接口
title: $ddt{title}
request:
method: post
url: $(read_config(base_ur1)}?s=user/login
data:
accounts: $ddt{accounts]
pwd: $ddt(pwd}
type: $ddt{type}
extract:
token: $.data.token
parametrize:
-["title","accounts","pwd","type","assert_code","assert_msg"]
- ["验证登陆成功测试用例","baili","baili123","username",0,"登录成功"]
- ["验证密码必须是6-18位","baili","123","username",-1,"密码格式 6~18 个字符"]
- ["验证错误的用户名",["baili"."北凡"],123,"username",-3,"登录帐号不存在"]
validate:
codes: 200
equals:
code: $ddt{assert_code}
contains: $ddt{assert_msg}
一、一级关键字必须要包含: name,base url,request,validate,在一级关键字request下必须包含:method ,url
二、传参方式:
1.get请求,那么必须通过params传参
2.post请求
传json格式,需要使用json传参
传表单格式,需要使用data传参
3.文件上传:使用files传参
三、接口关联
支持多种提取方式: 正则表达式和jsonpath表达式提取
extract:
access token:"access token":"(.*?)" #正则
expires in: $.expires in #jsonpath
取值方式: 其中read_extract_data是热加载的方法名,access_token是提取的变量名
${read_extract _data(access token)}
四、热加载
1.在hotloads目录下创建一个py文件并新建一个类(可以自定义),然后写方法,注意返回值需要字符串就返回字符串,需要整形就返回征整形,如下:
#获得随机数,需要字符串返回的字符串。
def get_random_number(self,min,max):
rm = random.randint(int(min), int(max))
return str(rm)
2.在测试用例当中传入类的对象,如DebugTalk()
@pytest.mark.parametrize("caseinfo".read_testcase_yaml('/testcases/product_manage/pm_create flag.yaml'))
def test_create_flag(self, caseinfo):
RequestsUtil(DebugTalk()).standard_yaml(caseinfo)
3.在YAML测试用例中调用函数: 如:
json:
{“tag”:[“name”:”码尚教育${get_random_number(10000,99999)}”}}
五、此框架支持两种断言方式: 分别为equals和contains断言
如:
validate:
- equals: {status code: 205}
- equals: {expires in: 7200}
- contains: access token
六、数据驱动使用yaml和一级关键字parameterize实现: 如:
测试用例yam1写法:
引入数据驱动文件
parameterize:
name-appid-secret-grant_type-assert_str:/datas/product manage/get_token_data.yaml
引入变量
$ddt{name}
数据驱动yam1写法:
#DDT(data driver test)数据驱动YAML文件,对应的YAML测试用例文件为:pm_get_token.yaml
prametrize:
- ['name', 'appid','secret','grant_type','assert_str']
- [成功获取token鉴权码','wx8a9de038e93f77ab','8326fc915928dee3165720c910effb86','client credential','accesstoken']
- ['检查appid为空','8326fc915928dee3165720c910effb86','client_credential',41002]
- ['检查secret为空','wx8a9de038e93f77ab','client_credential',41004]
七、基础路径的设置
在测试用例中使用一级关键字和热加载,如:
base_url: ${read_config(base,base_spgl_ur1)}