概述
开启pytest之旅
环境依赖
pytest(7.4.0)
pytest-html(3.2.0)
pytest-ordering(0.6)
pytest-xdist(3.3.1)
allure-pytest (2.13.2)
目录
│ conftest.py 维护固件fixtures
│ pytest.ini pytest配置文件
│ README.md
│ run.py 执行入口
│ test_class.py 用例脚本
│ test_func.py 用例脚本
│ test_mark_func.py 用例脚本
│ test_simulate_many_case.py 用例脚本
│
├─report pytest-html报告目录
│ │ report.html
│ │
│ └─assets
│ style.css
脚本
-
用例
pytest收集用例规则
"""测试用例方法在类里面"""class ClassOne():def test_class_one_1(self): # 不执行,类没有以Test开头assert Truedef test_class_one_2(self): # 不执行,类没有以Test开头assert type('name') is strclass TestClassTwo():def test_class_two_1(self): # 执行assert Truedef class_two_2(self): # 不执行,方法未以test开头assert True"""测试用例方法不在类里面"""def test_one(): # 执行assert 1 == 1def two(): # 不执行,方法命名未以test开头assert 'a' in 'bac' -
固件fixture
统一维护到
conftest.py
中,固件scope分类:function、session、module、class,默认是function级别。起到类似于tearup teardown的作用。import pytest@pytest.fixture()def func_fixture_noauto(): # 需要用到此fixture的用例,在参数中传入,如:def test_one(func_fixture_noauto):print('这是不自动引用func_fixture的开始')yieldprint('这是不自动引用func_fixture的结束')@pytest.fixture(autouse=True)def func_fixture():print('这是func_fixture的开始')yieldprint('这是func_fixture的结束')@pytest.fixture(scope='session', autouse=True)def session_fixture():print('这是session_fixture的开始')yieldprint('这是session_fixture的结束')@pytest.fixture(scope='module', autouse=True)def module_fixture():print('这是module_fixture的开始')yieldprint('这是module_fixture的结束')@pytest.fixture(scope='class')def class_fixture(): # class范围的fixture需要在测试class的地方显式调用:@pytest.mark.usefixtures('class_fixture')print('这是class_fixture的开始')yieldprint('这是class_fixture的结束')# 固件参数化,可以对固件重命名,使用如:def test_func_three(rs, fixture_param),从而实现参数化@pytest.fixture(params=[('Sam', 18), ('Tom', 30), ('David', 45), ('John', 12)], name='fixture_param')def param(request):"""固件参数化"""return request.param -
跳过
import pytest# 跳过@pytest.mark.skip(reason='跳过测试')def test_two():assert 'a' in 'bac'# 条件跳过@pytest.mark.skipif(pytest.__version__ < '8', reason='版本小于7')def test_three():assert 4 > 5 -
参数化
import pytest@pytest.mark.parametrize(('var', 'pwd'), [(1, 9), (2, 4), (3, 9)])def test_five(var, pwd):assert var + pwd < 11 -
预见失败
import pytest"""xfail 标记时,条件为True时,执行结果标记为Xpass或者xfail,x表示预见"""@pytest.mark.xfail(pytest.__version__ < '8', reason='不支持的版本') # 当前pytest版本为7.4.0def test_four():assert True# 执行结果为:XPASS (不支持的版本)@pytest.mark.xfail(pytest.__version__ < '8', reason='不支持的版本') # 当前pytest版本为7.4.0def test_four():assert False# 执行结果为:XFAIL (不支持的版本)"""xfail 标记时,条件为False时和普通未xfail标记用例执行结果一样"""@pytest.mark.xfail(pytest.__version__ < '7', reason='不支持的版本') # 当前pytest版本为7.4.0def test_four():assert True# 执行结果为:PASSED@pytest.mark.xfail(pytest.__version__ < '7', reason='不支持的版本') # 当前pytest版本为7.4.0def test_four():assert False# 执行结果为:FAILED -
标记
"""标记执行函数的三种方式方式一:指定函数执行,【pytest ./test_mark_func.py::test_marker_func_two】,仅执行test_marker_func_two,弊端是一次只能指定一个函数,不支持批量方式二:模糊匹配,使用 -k 选项标识,【pytest -k marker ./test_mark_func.py】,执行test_marker_func_one和test_marker_func_two,支持批量,弊端是函数名需包含相同的模式,不怎么方便方式三:@pytest.mark.xx 进行标记,执行时使用 -m 选项标记。需在pytest.ini配置文件中注册mark(1)、执行某个标记,【pytest -m smoke ./test_mark_func.py】,执行方法test_mark_func_three和test_mark_func_five(2)、标记支持 and or not,【pytest -m "merge and smoke" ./test_mark_func.py】,执行方法test_mark_func_five;【pytest -m "merge or smoke" ./test_mark_func.py】,执行方法test_marker_func_two、test_mark_func_three、test_mark_func_five【pytest -m "merge and not smoke" ./test_mark_func.py】执行方法test_marker_func_two"""import pytestdef test_marker_func_one():assert True@pytest.mark.mergedef test_marker_func_two():print('merge mark')assert True@pytest.mark.smokedef test_mark_func_three():print('smoke mark')assert Truedef test_mark_func_four():assert True@pytest.mark.merge@pytest.mark.smokedef test_mark_func_five():print('two mark')assert Truedef test_mark_func_six():assert True备注:pytest.ini文件中需要注册标记,未注册时,执行会报PytestUnknownMarkWarning。pytest执行参数可加入
--strict-markers
,执行时有未注册的mark,报错:'XXXX' not found in markers configuration option# pytest 配置文件[pytest]# mark 注册markers =smoke : '标记为冒烟用例'merge : '标记为合并代码时执行的用例' -
失败重试
import pytest# 失败重试 ,需安装pytest-rerunfailures【待探索,如果用pytest-xdist分布式执行脚本,pytest-rerunfailures报错connection refused】# 方式一:命令行失败重试 pytest -v --reruns 2 --reruns-delay 5 test_rerun.py# 方式二:脚本中失败重试@pytest.mark.flaky(reruns=2, reruns_delay=3) # 失败重试两次,每次延时3秒执行def test_six():assert 'a' in ['b', 'c'] -
设置执行顺序
pytest默认是从上到下依次执行用例,如果需要设置用例执行的顺序需要使用pytest-ordering插件,建议用例尽量不要有执行顺序的限制
import pytest@pytest.mark.run(order=2) # 第2个执行def test_four():assert False# 设置执行顺序,需安装pytest-ordering@pytest.mark.parametrize(('var', 'pwd'), [(1, 9), (2, 4), (3, 9)])@pytest.mark.run(order=1) # 第1个执行def test_five(var, pwd):assert var + pwd < 11 -
断言
利用python自带的
assert
-
分布式执行
pytest默认是单进程执行用例脚本,当用例数量比较多时,单进程执行耗时会比较长。可以用pytest-xdist分布式执行用例。
方式一:本地多进程执行 pytest -n X,X代表指定用于执行的cup个数,一般是服务器核数的1/2。X也可以指定为auto,即使用电脑的所有核数
方式二:远程分布式执行
(1)ssh 方式
pytest -d --tx ssh=user_name@server_ip//python=指定服务器的python解释器路径//chdir=指定服务器的工作路径 --rsyncdir=本地需同步的目录 --tx sh=user_name@server_i p//python=指定服务器的python解释器路径//chdir=指定服务器的工作路径
如:pytest -d --rsyncdir=./ --tx ssh=root@123.57.138.224//python=/usr/python37/bin/python3.7//chdir=/usr/python-script
前置条件:使用ssh方式前,需要配置免密登录服务器
1.windows本机生成私钥、公钥,命令行执行:ssh-keygen,提示输入密码直接回车。密钥生成的目录C:/Users/dearw/.ssh/id_rsa(私钥),C:/Users/dearw/.ssh/id_rs a.pub(公钥)
2.将公钥拷贝到linux服务器,命令行执行:ssh-id-copy username@remote-server后,输入username的密码。拷贝到 cd ~/.ssh/authorized_keys
3.检查是否设置成功,重新打开cmd窗口,输入:ssh username@remote-server回车,如果远程登录成功,则开通成功
(2)socket方式
前置条件:开启worker执行机的socket服务,python3 /usr/python37/lib/python3.7/site-packages/execnet/script/socketserver.py
pytest -d -m smoke --rsyncdir G:/src/pytest-template --tx socket=123.57.138.224:5000 pytest-template
,不用指定python编译器和工作目录chdir
备注:未指定工作目录chdir时,目录为当前用户目录下创建pyexecnetcache,如/root/pyexecnetcache/
-
测试报告
方式(1)需安装pytest-html插件。
执行参数中加入
--html G:/src/pytest-template/report/report.html
报告中文乱码解决办法:修改
pytest-html包->html_report.py->方法_generate_report->head = html.head(html.meta(charset="utf-8"), html.title(self.title), html_css)
中utf-8
改成GB2312
方式(2)需安装allure环境和allure-pytest插件。
-
脚本执行入口
run.py
import pytestdef run():pytest.main([# "-n","auto",# "-s", # stream输出print内容"-m", "smoke and merge", # 执行标记为smoke且为merge的用例"--strict-markers", # 检查是否存在未注册的标记mark"-d", # 分布式执行,需安装pytest-xdist"--rsyncdir", "G:/src/pytest-template", # 分布式执行,需要同步的目录,支持多个# "--tx","ssh=root@123.57.138.224//python=/usr/python37/bin/python3//chdir=/usr/python-script", # 分布式执行,ssh方式;指定了python编译器;指定工作目录# "--tx", "ssh=root@123.57.138.224//python=/usr/python37/bin/python3", # 分布式执行,ssh方式;指定了python编译器;未指定工作目录"--tx", "socket=123.57.138.224:5000", # 分布式执行,socket方式"--rsyncignore", "*.pyc", # 分布式执行,不同步的文件"--rsyncignore", "*.md", # 分布式执行,不同步的文件"pytest-template", # 分布式执行脚本的根目录"--html", "G:/src/pytest-template/report/report.html", # 输出html报告,需安装pytest-html])if __name__ == "__main__":run() -
命令行启动测试
pytest -d -m smoke --rsyncdir G:/src/pytest-template --tx socket=123.57.138.224:5000 pytest-template
,可根据项目需要添加相应的参数 -
配置文件维护pytest执行参数
在pytest.ini中增加一行
addopts = --strict-markers -s --tx ssh=root@123.57.138.224//python=/usr/python37/bin/python3//chdir=/usr/python-script --rsyncdir=./ pytest-template
扩展
- pytest-suger,显示进度条,立即输出错误。
pip install pytest-suger
,安装后用pytest命令行执行,即生效。备注:pytest.main([])
执行不生效 - pytest-tldr,简化stream输出,安装后用pytest命令行执行,即生效。备注:
pytest.main([])
执行生效