测开-pytest框架(Python)
参考链接一:https://blog.csdn.net/lovedingd/article/details/98952868
参考链接二:https://www.cnblogs.com/shenh/p/11572657.html
参考链接三:https://www.cnblogs.com/poloyy/category/1690628.html
一、用例的识别与运行
用例编写规范
- 测试文件以test_开头(以_test结尾也可以)
- 测试类以Test开头,并且不能带有init方法
- 测试函数以test_开头
- 断言使用基本的assert即可
运行参数 pytest --help
- 无参数,读取路径下所有符合规则的文件,类,方法,函数全部执行----pytest或者py.test
- -v参数,打印详细运行日志信息----pytest -v
- -s参数,将代码中的print输出打印到控制台----pytest -s
- -k参数,跳过运行某个或某些用例不运行
pytest -k '类名' pytest -k '方法名' pytest -k '类名 and not 方法名' # 运行类里不包含某个方法的其他所有方法
- -x参数,遇到用例失败立即停止运行----pytest -x
- --maxfail参数,用例失败达到阀值停止运行----pytest --maxfail=[num]
- -m参数,只运行有@pytest.mark.[标记名]这个标记的测试用例
# 在运行的时候可能只运行某个功能的所有测试用例,那就在该功能的所有测试用例方法上加上装饰器:@pytest.mark.[标记名],例如:@pytest.mark.login; # 运行的时候使用命令添加一个-m参数,例如:pytest -m login来只执行登录相关测试用例;
运行模式
- pytest 文件名.py
- pytest 文件名.py::类名
- pytest 文件名.py::类名::方法名

注意ini文件中的缩进!


二、pytest框架结构
- 模块级(setup_module/teardown_module)在模块始末调用
- 函数级(setup_function/teardown_function)在函数始末调用(在类外部)
- 类级(setup_class/teardown_class)在类始末调用(在类中)
- 方法级(setup_method/teardown_method)在方法始末调用(在类中)
- 方法级(setup/teardown)在方法始末调用(在类中)
- 调用顺序:setup_module > setup_class > setup_method > setup > teardown > teardown_method > teardown_class > teardown_module
def setup_module(): print("\nsetup_module,只执行一次,当有多个测试类的时候使用") def teardown_module(): print("\nteardown_module,只执行一次,当有多个测试类的时候使用") class TestPytest1(object): @classmethod def setup_class(cls): print("\nsetup_class,只执行一次") @classmethod def teardown_class(cls): print("\nteardown_class,只执行一次") def setup_method(self): print("\nsetup_method,每个测试用例都执行一次") def teardown_method(self): print("\nteardown_method,每个测试用例都执行一次") def setup(self): print("\nsetup,是不是在每个用例都执行一次?") def teardown(self): print("\nteardown,是不是在每个用例都执行一次?") def test_three(self): print("test_three,测试用例") def test_four(self): print("test_four,测试用例")
三、控制用例的执行顺序
pytest加载所有的测试用例是乱序的,项指定用例的顺序,可以使用:pytest-ordering插件,只需要在测试用例加上装饰器;
# @pytest.mark.run(order=num),设置num的值就可以按照num的大小顺序来执行,主要:-1是最后一条 import pytest import pytest_ordering class TestOrder(): @pytest.mark.run(order=2) def test_two(self): print('test_two') @pytest.mark.run(order=0) def test_zero(self): print('test_zero') @pytest.mark.run(order=-1) def test_four(self): print('test_four') @pytest.mark.run(order=1) def test_one(self): print('test_one') """ test_order.py::TestOrder::test_zero PASSED [ 25%]test_zero test_order.py::TestOrder::test_one PASSED [ 50%]test_one test_order.py::TestOrder::test_two PASSED [ 75%]test_two test_order.py::TestOrder::test_four PASSED [100%]test_four """
四、pytest fixture
pytest中可以使用@pytest.fixtrue装饰器来装饰一个方法,被装饰的方法名可以作为一个参数传入到测试方法中。可以使用这种方法来完成测试之前的初始化,也可以返回数据给测试函数。
将fixture作为函数参数
1、在某方法(login)加上@pytest.fixture装饰器后,将这个方法名以参数的形式传入到其他测试用例方法中,那么这个方法(login)就会先执行,然后再去执行自身的测试步骤,如果没有传入这个方法(login),就不会执行这个方法(login),而直接执行已有的步骤。
2、被fixture装饰的test方法(测试用例)不会被当成测试用例执行,只会被作为另一个用例的参数的时候会被执行一次单独执行test_case1的时候会报错。
import pytest @pytest.fixture() def login(): print("这是登录方法\n") return ('echo','123') @pytest.fixture() def operate(): print("这是登录后的操作\n") # 被fixture装饰的test方法不会被当成测试用例执行,只会被作为另一个用例参数的时候会被执行一次 # 单独执行test_case1的时候会报错 @pytest.fixture() def test_case1(): print("test_case1,不需要登录\n") def test_case2(login,operate): print(login) print("test_case2,需要登录和登录后的操作\n") def test_case3(test_case1): print("test_case3,需要先执行case1\n")
"""
pytest_demo\test_fixtrue.py 这是登录方法
这是登录后的操作
('echo', '123')
test_case2,需要登录和登录后的操作
.test_case1,不需要登录
test_case3,需要先执行case1
.
"""
指定范围内共享-scope
fixture里有一个scope参数,默认值为:function,通过scope可以控制fixture的作用范围,根据作用范围大小划分:session > module > class > function;
- session:是多个文件调用一次(可以跨.py文件调用,每个.py文件就是一个module)
- module:模块级调用一次
- class:类级别调用一次
- function:函数或者方法级别都会被调用
注意:
1、通过fixture装饰器装饰的方法,可以直接将方法名作为用例的参数传入来调用方法,或者通过@pytest.mark.usefixtures("方法名")来调用方法。
2、如果用例出现异常,不会影响yield后面的代码执行。
3、注意使用被fixture装饰方法的位置,虽然是某级别只执行一次,但是由于pytest执行用例是乱序的,所以被装饰的方法不一定在需要的时候执行(比如最开始就执行),那么就需要指定用例的执行顺序或者是让所有用例都使用被装饰的方法作为参数(如果用例几百条的时候就不现实)。
test_fixtrue.py
import pytest @pytest.fixture(scope="session") def open(): print("打开浏览器") yield print("执行teardown!") print("最后关闭浏览器") @pytest.fixture() def login(): print("这是登录方法\n") return ('echo','123') @pytest.fixture() def operate(): print("这是登录后的操作\n") # 被fixture装饰的test方法不会被当成测试用例执行,只会被作为另一个用例参数的时候会被执行一次 # 单独执行test_case1的时候会报错 # @pytest.fixture() @pytest.mark.usefixtures("open") def test_case1(): print("test_case1,不需要登录\n") def test_case2(login,operate): print(login) print("test_case2,需要登录和登录后的操作\n") def test_case3(): print("test_case3,需要先执行case1\n")
import pytest @pytest.fixture(scope='module') def get_dirver(): print("获取driver") yield print("释放driver") # @pytest.mark.usefixtures("get_dirver") def test_search1(): print("test_search1") # raise NameError # print("test_search1--raise之后") # @pytest.mark.usefixtures("get_dirver") def test_search2(get_dirver): print("test_search2") @pytest.mark.usefixtures("get_dirver") def test_search3(): print("test_search3")
输出结果-----------------------------------------------------------------------------------------------------------------------
pytest_demo\test_fixtrue.py 打开浏览器 test_case1,不需要登录 .这是登录方法 这是登录后的操作 ('echo', '123') test_case2,需要登录和登录后的操作 .test_case3,需要先执行case1 . pytest_demo\test_fixture_scope.py test_search1 .获取driver test_search2 .test_search3 .释放driver 执行teardown! 最后关闭浏览器
conftest.py文件
fixture scope为session级别时可以跨.py模块调用的,也就是当我们有多个.py文件的用例时,如果多个用例只需要调用一次fixture,可以将scope='session',并且写到conftest.py文件中。写到conftest.py文件可以全局调用这里面的方法。使用的时候不需要导入conftest.py这个文件。
在运行整个项目下的所有的用例,只执行一次打开浏览器。执行完所有的用例之后再执行关闭浏览器,可以在这个项目下创建一个conftest.py文件,将打开浏览器操作的方法放在这个文件下,并添加一个装饰器@pytest.fixture(scope='session'),就能够实现整个项目所有的测试用例的浏览器复用。
应用场景:
- 每个接口(用例)需共用到的token
- 每个接口(用例)需共用到的测试用例数据
- 每个接口(用例)需共用到的配置信息
使用规则:
- conftest.py这个文件名是固定的,不可以更改;
- conftest.py与运行的用例在同一个包下,并且该包包含__init__.py文件;
- 使用的时候不需要导入conftest.py,pytest会自动识别到这个文件;
- 放到项目的根目录下可以全局调用,放到某个package下,就在这个package下生效;
conftest.py
import pytest @pytest.fixture(scope='session') def open(): print('打开浏览器') yield print('执行teardown!') print('关闭浏览器')
test_scope1.py
def test_search1(open): print('test_search1') def test_search2(): print('test_search2') def test_search3(): print('test_search3')
test_scope2.py
import pytest class TestScope(): # @pytest.mark.usefixtures('open') def test_case1(self): print('test_case1') def test_case2(self): print('test_case2') def test_case3(self): print('test_case3')
执行结果:
pytest_demo\test_scope1.py 打开浏览器
test_search1
.test_search2
.test_search3
.
pytest_demo\test_scope2.py test_case1
.test_case2
.test_case3
.执行teardown!
关闭浏览器
自动执行fixture
如果每条测试用例都需要添加fixture功能,则需要在每一个用例方法里面传入这个fixture的名字--很不方便;这里可以在装饰器里面添加一个参数autouse='true'(默认是false),它会自动应用到所有的测试方法中,测试用例就无须再传入这个fixture的名字,它会自动在每条用例之前执行这个fixture,只是这样fixture没法提供返回值给测试用例。
执行结果:
import pytest @pytest.fixture(scope='module',autouse='true') def get_dirver(): print("获取driver") yield print("释放driver") # @pytest.mark.usefixtures("get_dirver") def test_search1(): print("test_search1") # raise NameError # print("test_search1--raise之后") # @pytest.mark.usefixtures("get_dirver") def test_search2(): print("test_search2") # @pytest.mark.usefixtures("get_dirver") def test_search3(): print("test_search3") """ pytest_demo\test_fixture_scope1.py 获取driver test_search1 .test_search2 .test_search3 .释放driver """
fixture传递参数
在测试过程中会将测试用例用到的数据以参数的形式传入到测试用例中,并未每条测试数据生成一个测试结果数据。这时候可以使用fixture的参数化功能,在fixture方法加上装饰器@pytest.fixture(params=[1,2,3],就会传入三个数据1、2、3,分别将这三个数据传入到用例中,可以传入的数据是个列表,传入的数据需要使用一个固定的参数名request来接收。
- 格式:@pytest.fixture(params=[1,2,3]
- params参数必须是个列表类型
- 使用固定的request来接收
- return必须是固定的param字段:request.param
import pytest # @pytest.fixture(params=[1,2,3]) @pytest.fixture(params=[ {'username':'张三','pwd':'123'}, {'username':'李四','pwd':'456'}, {'username':'王五','pwd':'798'} ]) # request和request.param是固定写法,不然报错 def data(request): return request.param def test_data(data): # print(f'测试数据:{data}') print(f'username={data["username"]},pwd={data["pwd"]}') # assert data < 5 """ username=张三,pwd=123 .username=李四,pwd=456 .username=王五,pwd=798 """
多线程并行与分布式执行
pytest-xdist是pytest分布式执行插件,可以多个CPU或主机执行,它允许用户将测试并发执行(进程级并发),插件是动态决定测试用例执行顺序的,为了保证各个测试能在各个独立线程里正确的执行,应该保证测试用例的独立性(这也是符合测试用例设计的最佳实践)。
- pytest -n auto:会自动检测系统CPU数目
- pytest -n [num]:为数字则指定运行测试的处理器进程数
结合pytest-html生成测试报告
执行完pytest测试用例,可以使用pytest-html插件生成HTML格式的测试报告。
- 执行方法:pytest --html=path/to/html/report.html
- 结合pytest-xdist使用:pytest -v -s -n 3 --html=report.html --self-contained-html
使用parametrize实现参数化
- parametrize()方法源码:
def parametrize(self,argnames, argvalues, indirect=False, ids=None, scope=None):
-
- 主要参数说明:
-
argnames:字符串类型参数名,如果中间用逗号分隔则表示为多个参数名
-
argvalues:列表类型参数值,列表中有几个元素,就会生成几条用例
-
- 主要参数说明:
- 使用方法:
- 使用@pytest.mark.parametrize()装饰器测试方法
- parametrize('data1,data2...',[param1,param2...])中的data是自定义的字符串参数名,param是参数列表
- 将自定义的参数名data作为参数传给测试盈利test_func
- 然后就可以换在测试用例内部使用data的参数
-
argvalues中数据定义id:


import pytest @pytest.mark.parametrize("test_input,expected",[("6+5",11),("4+8",12),("1+6",7)]) def test_add(test_input,expected): print(f'test_input={test_input},expected={expected}') assert eval(test_input) == expected """ test_input=6+5,expected=11 .test_input=4+8,expected=12 .test_input=1+6,expected=7 """
多次使用parametrize
同一个测试用例可以添加多个@pytest.mark.parametrize()装饰器,多个parametrize的所有元素互相组合,生成大量测试用例。
import pytest @pytest.mark.parametrize('x',[2,8]) @pytest.mark.parametrize('y',[1,3]) @pytest.mark.parametrize('z',[9,7,8]) def test_foo(x,y,z): print(f'测试数据x:{x},y:{y},z:{z}') """ 测试数据x:2,y:1,z:9 .测试数据x:8,y:1,z:9 .测试数据x:2,y:3,z:9 .测试数据x:8,y:3,z:9 .测试数据x:2,y:1,z:7 .测试数据x:8,y:1,z:7 .测试数据x:2,y:3,z:7 .测试数据x:8,y:3,z:7 .测试数据x:2,y:1,z:8 .测试数据x:8,y:1,z:8 .测试数据x:2,y:3,z:8 .测试数据x:8,y:3,z:8 """
@pytest.fixture和@pytest.mark.parametrize结合实现参数化
如果测试用例数据需要在fixture方法中使用,同时也需要在测试用例中使用,可以在使用parametrize的时候添加一个参数indirect=True,pytest可以实现将参数传入到fixture方法中,也可以在当前的测试用例中使用。
parametrize()方法源码:
def parametrize(self,argnames, argvalues, indirect=False, ids=None, scope=None):
- indirect参数设置为True,pytest会把argnames当做函数去执行,将argvalues作为参数传到argnames这个函数中。
import pytest @pytest.fixture(scope='module') def login_r(request): user = request.param print(f'数据:{user["username"]},{user["pwd"]}') return user test_user_data = [ {'username':'张三','pwd':'123'}, {'username':'李四','pwd':'456'}, {'username':'王五','pwd':'798'} ] @pytest.mark.parametrize("login_r",test_user_data,indirect=True) def test_login(login_r): u = login_r print(f"用例中login_r的返回值:{u}") assert u != '' """ 数据:张三,123 用例中login_r的返回值:{'username': '张三', 'pwd': '123'} .数据:李四,456 用例中login_r的返回值:{'username': '李四', 'pwd': '456'} .数据:王五,798 用例中login_r的返回值:{'username': '王五', 'pwd': '798'} """
pytest结合YAML
yaml是一个可读性高,用来表达数据序列化的格式。pyyaml模块在Python中用于处理yaml格式数据,主要使用yaml.safe_dump()和yaml.safe_load()函数将Python值和yaml格式数据相互转换。
import pytest import yaml @pytest.mark.parametrize('a,b',yaml.safe_load(open('data.yml',encoding='utf-8'))) def test_foo(a,b): print(f'a+b={a+b}') """ test_yaml.py::test_foo[1-2] PASSED [ 50%]a+b=3 test_yaml.py::test_foo[20-30] PASSED [100%]a+b=50 """
结合allure生成测试报告
- 电脑安装allure并配置环境变量,通过allure --version查看是否安装配置成功;
- 在pycharm中安装allure_pytest
- 执行和打开方式一:
- 执行测试用例将结果保存到中间文件中:pytest testcase/test_pytest_allure/test_calc.py --alluredir allure-report
- 以html形式查看测试报告:allure serve allure-report
- 执行和打开方式二:
- 在pytest执行测试的时候,指定参数-alluredir选项及结果数据保存的目录,如下:
pytest --alluredir=tmp/my_allure_results
tmp/my_allure_results中保存了本次测试的结果数据。
-
- 打开报告,需要启动allure服务,在terminal中输入allure serve [path/to/allrue_results],如下:
allure serve path/to/allrue_results
- 执行和打开方式三:
- 也可以使用allure generate生成html格式的测试结果报告,并使用allure open来打开报告。
allure generate ./result/ -o ./report --clean
上面的命令将./result/目录下的测试数据生成HTML测试报告到./report路径下,--clean选项目的是先清空测试报告目录,再生成新的测试报告,
然后使用下面的命令打开报告。
allure open -h 127.0.0.1 -p 8883 ./report
上面这个命令则会启动一个web服务将已经生成的测试报告在默认浏览器打开。
浙公网安备 33010602011771号