赞
踩
Pytest是一个全功能Python测试工具,支持第三方扩展插件,能够使用其开展单元测试和复杂功能测试。可以和selenium、requests、appium等模块结合使用实现WEB UI、API、APP自动化测试。
详见参考指南文档:https://docs.pytest.org/en/7.1.x/#
PDF文档 : https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf
0、前提:已安装配置好Python 3.7+环境
1、安装:在命令行或Pycharm终端中运行命令 pip install pytest
2、验证:运行命令 pytest --version
,会显示已安装的pytest版本号
按照Pytest发现用例规则编写用例直接运行,便能够调用Pytest收集符合规则的测试用例,执行用例,打印执行情况等信息。
""" Pytest运行demo """ class TestPytest(): """测试Pytest类""" def test_001(self): """test_001""" print("test_001") assert 1 == 1 def test_002(self): """test_002""" print("test_002") assert 1 < 2 def test_003(self): """test_003""" print("test_003") assert 1 != 2 def test_004(self): """test_004""" print("test_004") assert 1 == 0
下图中红框部分给出了测试结果信息,包含了每条用例的执行结果和耗时、通过和失败数量等;蓝色部分为启动信息;绿框部分呈现了执行过程、断言失败等信息。
pytest.ini文件优先于其他文件,即使是空的,执行的各类参数可在此文件进行配置,详见下文各示例内容。
pytest.main(['-v'])
,详细展示每条用例方法名称、结果、进度等信息pytest.main(['-s'])
pytest.main(['-q', '../Testcase/test_pytest_demo.py'])
pytest.main(['-vs', '../Testcase/test_pytest_demo.py','-n2'])
,n后面的数字为线程数,使用前需要安装pytest-xdist插件pytest.main(['-m markName'])
,自定义的标记名称可在pytest.ini文件中进行注册才可以使用pytest.main(['-v', '../Testcase/test_pytest_demo.py', '--lf'])
,如果上次没有失败用例,则执行全部
pytest.main(['-q', '--reruns=1', '../Testcase/test_pytest_demo.py'])
pytest.main()
pytest.main(['../Testcase'])
pytest.main(['../Testcase/test_pytest_demo.py'])
pytest.main(['../Testcase/test_pytest_demo.py::TestPytest'])
pytest.main(['../Testcase/test_pytest_demo.py::TestPytest::test_001'])
[pytest]
testpaths = ../TestCase
python_files = test_*.py
python_classes = Test*
python_functions = test_*
通过@pytest.fixture
装饰器可以实现用例执行的前后置条件设置。在被装饰的方法中,通过yield的使用可实现前后置条件功能,yield具有return的返回功能,但yield后面的代码是可以被执行的(如果函数A有yield,执行时会被认为是一个生成器,函数A执行到yield时,会跳出函数A,去执行调用了A函数的代码段B,代码段B执行完后,继续回到函数A执行yield后的代码)。
fixture设置方式:
@pytest.fixture(scope=scope, params=Iterable[object], autouse=True, ids=Iterable[object], name=fixtureName)
fixture调用方式:
@pytest.mark.usefixtures('fixtureName1', 'fixtureName2')
@pytest.fixture(autouse=True)
fixture设置与调用示例代码:
""" Pytest fixture装置使用示例 """ import pytest @pytest.fixture def human(): """未设置参数,默认为用例级,谁调用谁享用""" name = '小明' print('前置条件:小明醒了,未设置参数,谁调用谁享用') yield name print('后置条件:小明睡了,未设置参数,谁调用谁享用') @pytest.fixture(autouse=True) def weather(): """作用范围:默认为用例级,设置自动调用为True,每个用例都会调用""" sun = '晴天' print('前置条件:天亮了autouse=True,默认作用每个用例') yield sun print('后置条件:天黑了autouse=True,默认作用每个用例') @pytest.fixture(scope='class') def time(): """作用范围:类级,谁调用谁享用""" t1 = '6:00' t2 = '20:00' print('前置条件:早上好class') yield t1, t2 print('后置条件:晚上好class') @pytest.fixture(scope='session', autouse=True) def session(): """作用范围:会话级,yield后可不写内容,不返回数据,设置自动调用为True""" print('前置条件:会话开始-----------------') yield print('后置条件:会话结束-----------------') @pytest.fixture(params=['小美', '小帅'], ids=('mei', 'shuai'), name='clm') def params_classmate(request): """作用范围:默认用例级,谁调用谁享用params和ids""" param = request.param print(f'前置条件:{param}') yield param print(f'后置条件:{param}') class TestOneDay1: """调用方式一,直接作为参数传给测试用例:human,time,weather,clm""" def test_eat(self, human, time, clm): t1, t2 = time print(human + t1 + f'和{clm}吃了早餐') def test_school(self, weather, human): print('今天是'+weather+','+human + '去上学了') @pytest.mark.usefixtures('human','time') class TestOneDay2: """调用方式二:通过装饰器调用'human'和'time',但无法传递参数""" def test_homework(self): print('交了作业') def test_home(self): print('放学独自回家') class TestOneDay3: """没有直接调用fixture,但设置了自动调用的会被调用""" def test_water(self): print('喝了水') def test_wc(self): print('去了wc') if __name__ == '__main__': pytest.main(['-s'])
运行结果:
============================= test session starts ============================= collecting ... collected 7 items test_pytest_fixture_demo.py::TestOneDay1::test_eat[mei] 前置条件:会话开始----------------- 前置条件:早上好class 前置条件:天亮了autouse=True,默认作用每个用例 前置条件:小明醒了,未设置参数,谁调用谁享用 前置条件:小美 PASSED [ 14%]小明6:00和小美吃了早餐 后置条件:小美 后置条件:小明睡了,未设置参数,谁调用谁享用 后置条件:天黑了autouse=True,默认作用每个用例 test_pytest_fixture_demo.py::TestOneDay1::test_eat[shuai] 前置条件:天亮了autouse=True,默认作用每个用例 前置条件:小明醒了,未设置参数,谁调用谁享用 前置条件:小帅 PASSED [ 28%]小明6:00和小帅吃了早餐 后置条件:小帅 后置条件:小明睡了,未设置参数,谁调用谁享用 后置条件:天黑了autouse=True,默认作用每个用例 test_pytest_fixture_demo.py::TestOneDay1::test_school 前置条件:天亮了autouse=True,默认作用每个用例 前置条件:小明醒了,未设置参数,谁调用谁享用 PASSED [ 42%]今天是晴天,小明去上学了 后置条件:小明睡了,未设置参数,谁调用谁享用 后置条件:天黑了autouse=True,默认作用每个用例 后置条件:晚上好class test_pytest_fixture_demo.py::TestOneDay2::test_homework 前置条件:早上好class 前置条件:天亮了autouse=True,默认作用每个用例 前置条件:小明醒了,未设置参数,谁调用谁享用 PASSED [ 57%]交了作业 后置条件:小明睡了,未设置参数,谁调用谁享用 后置条件:天黑了autouse=True,默认作用每个用例 test_pytest_fixture_demo.py::TestOneDay2::test_home 前置条件:天亮了autouse=True,默认作用每个用例 前置条件:小明醒了,未设置参数,谁调用谁享用 PASSED [ 71%]放学独自回家 后置条件:小明睡了,未设置参数,谁调用谁享用 后置条件:天黑了autouse=True,默认作用每个用例 后置条件:晚上好class test_pytest_fixture_demo.py::TestOneDay3::test_water 前置条件:天亮了autouse=True,默认作用每个用例 PASSED [ 85%]喝了水 后置条件:天黑了autouse=True,默认作用每个用例 test_pytest_fixture_demo.py::TestOneDay3::test_wc 前置条件:天亮了autouse=True,默认作用每个用例 PASSED [100%]去了wc 后置条件:天黑了autouse=True,默认作用每个用例 后置条件:会话结束----------------- ============================== 7 passed in 0.02s ============================== 进程已结束,退出代码0
在上面的示例中,fixture装置下的方法和测试用例写在了同一个文件内,而在实际的应用中,是要单独写在一个py文件内容,这时便可以通过conftest.py文件的帮助,更好的管理这些fixture装置方法(前后置条件用例)。
conftest.py文件是为整个目录提供固定装置的一种方法,可以将fixture装置写入到该文件中,使用时无需导入,在运行测试用例时,pytest会自动去发现该文件。但需要注意的是conftest.py的作用范围是其所在目录及子目录内的所有用例(例如所有的测试用例都在TestCase目录内,那么conftest.py文件需要放在TestCase目录内)
conftest.py示例代码:
"""
测试conftest.py文件
"""
import pytest
@pytest.fixture(autouse=True)
def test_conftest():
print("conftest.py中的前置条件")
yield
print("conftest.py中的后置条件")
测试用例代码:
""" conftest.py文件使用 """ import pytest class TestPytest(): """测试Pytest类""" def test_001(self): """test_001""" print("test_001") a = "a" assert a == a def test_002(self): """test_002""" print("test_002") assert 1 < 2
运行结果:
..\Testcase\test_pytest_demo.py::TestPytest::test_001 conftest.py中的前置条件
test_001
PASSEDconftest.py中的后置条件
..\Testcase\test_pytest_demo.py::TestPytest::test_002 conftest.py中的前置条件
test_002
PASSEDconftest.py中的后置条件
============================== 2 passed in 0.01s ==============================
进程已结束,退出代码0
通过装饰器@pytest.mark.parametrize('paramNameN', 'paramValue')
可以实现测试数据的参数化,达到数据驱动的目的,参数名直接以字符串的形式传入,参数值可以是元组、列表、函数(自定义函数需要以列表形式传入)等。也可以通过与csv、yaml文件结合使用实现数据驱动测试。
参数化示例代码:
""" Pytest @pytest.mark.parametrize参数化装饰器使用示例 """ import random import pytest def random_num(): return random.randint(88888888, 99999999) class TestParametrize(): """@pytest.mark.parametrize参数化装饰器""" @pytest.mark.parametrize('name', ('小明', '小帅', '小黑')) def test_001(self, name): """1个参数""" print(f"{name}") assert 1 == 1 @pytest.mark.parametrize('name, age', [('小明', '18'), ('小帅', '17'), ('小黑', '16')]) def test_002(self, name, age): """多个参数""" print(f"{name}:{age}") assert 1 == 1 @pytest.mark.parametrize('caseinfo', [{'name': '小明', 'age': '18', 'except': 'true'}, {'name': '小美', 'age': '17', 'except': 'true'}]) def test_003(self, caseinfo): """以列表字典形式传入多组测试数据""" name = caseinfo['name'] age = caseinfo['age'] print(f"{name}:{age}") assert caseinfo['except'] == 'true' @pytest.mark.parametrize('num', [random_num()]) def test_004(self, num): """传入随机数""" print(f"中奖号码为:{num}") assert 1 == 1 @pytest.mark.parametrize('num', range(5)) def test_005(self, num): """传入一个range(函数)""" print(f"序号:{num}") assert 1 == 1 if __name__ == '__main__': pytest.main(['-s'])
运行结果:
============================= test session starts ============================= collecting ... collected 14 items test_pytest_mark_parametrize.py::TestParametrize::test_001[\u5c0f\u660e] PASSED [ 7%]小明 test_pytest_mark_parametrize.py::TestParametrize::test_001[\u5c0f\u5e05] PASSED [ 14%]小帅 test_pytest_mark_parametrize.py::TestParametrize::test_001[\u5c0f\u9ed1] PASSED [ 21%]小黑 test_pytest_mark_parametrize.py::TestParametrize::test_002[\u5c0f\u660e-18] PASSED [ 28%]小明:18 test_pytest_mark_parametrize.py::TestParametrize::test_002[\u5c0f\u5e05-17] PASSED [ 35%]小帅:17 test_pytest_mark_parametrize.py::TestParametrize::test_002[\u5c0f\u9ed1-16] PASSED [ 42%]小黑:16 test_pytest_mark_parametrize.py::TestParametrize::test_003[caseinfo0] PASSED [ 50%]小明:18 test_pytest_mark_parametrize.py::TestParametrize::test_003[caseinfo1] PASSED [ 57%]小美:17 test_pytest_mark_parametrize.py::TestParametrize::test_004[92025408] PASSED [ 64%]中奖号码为:92025408 test_pytest_mark_parametrize.py::TestParametrize::test_005[0] PASSED [ 71%]序号:0 test_pytest_mark_parametrize.py::TestParametrize::test_005[1] PASSED [ 78%]序号:1 test_pytest_mark_parametrize.py::TestParametrize::test_005[2] PASSED [ 85%]序号:2 test_pytest_mark_parametrize.py::TestParametrize::test_005[3] PASSED [ 92%]序号:3 test_pytest_mark_parametrize.py::TestParametrize::test_005[4] PASSED [100%]序号:4 ============================= 14 passed in 0.02s ============================== 进程已结束,退出代码0
通过在pytest.ini配置日志相关参数,便可实现日志功能,可配置的参数:
pytest.ini配置日志示例:
[pytest]
addopts = -vs --color=yes
log_cli = true
log_level = DEBUG
log_format = %(asctime)s %(filename)-s:%(lineno)-s %(levelname)-8s - %(message)s
log_date_format = %Y/%m/%d %H:%M:%S
log_auto_indent = true
log_file = ../Log/logs.log
log_file_level = DEBUG
log_file_format = %(asctime)s %(filename)-s:%(lineno)-s %(levelname)-8s - %(message)s
log_file_date_format = %Y/%m/%d %H:%M:%S
日志示例代码:
""" Pytest logging使用示例 """ from time import sleep import logging logger = logging.getLogger('__name__') def test_pytest_log(): logger.info('info msg') logger.debug('debug msg') logger.error('error msg') sleep(3) logger.critical('critical msg') sleep(3) logger.warning('warning msg') logger.info('info \n多行缩进\n多行缩进\n多行缩进') if __name__ == '__main__': test_pytest_log()
运行结果-控制台:
运行结果-日志文件:
可以直接使用python自带的断言,具体呈现形式,可见前面的示例代码。
assert a in b
:断言a包含bassert a == b
:断言a等于bassert 1 < 2
:断言1小于2方式1:安装三方插件pytest-allure,详细使用方法见Allure在Pytest自动化测试框架中的基本应用
方式2:安装三方插件pytest-html,执行是添加--html=../report/report.html
参数便可生产html报告
运行pytest.main(['-vs', '--html=../report/report.html', '../Testcase/test_pytest_report_html.py'])
说明:本文为学习笔记,如错误、可优化等内容,欢迎交流与指正。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。