【测试】Pytest框架、断言、fixture、mark、配置文件、常用插件

it2024-12-28  12

1.Pytest

Pytest 是一个很强大的python测试框架,里面有很多优秀的册数类,可以满足python开发中的各种测试,比如说可以标记跳过一些测试、支持重复失败的测试、支持断言等。不过它并非python内置,所以我们得另外安装

pip install -U pytest -i https://pypi.tuna.tsinghua.edu.cn/simple import pytest import requests def test_one(): r = requests.get('https://www.baidu.com') print(r.status_code) def test_two(): r = requests.get('https://www.baidu.com') print(r.encoding) class TestTask(object): def test_1(self): print('test 1') def test_2(self): print('test 2') if __name__ == '__main__': # pytest.main() # 不打印东西到屏幕 # pytest.main(["-s"]) # 打印到屏幕 pytest.main(['-s', 'test_1.py']) # 只执行某个文件(要以test开头) # 相当于在终端执行:pytest -s test_1.py

2.断言和警告

# 断言 pytest.raises(expected_exception, match=None, kwargs) # 警告 pytest.warns(expected_warning, *args, match, **kwargs) import pytest def is_leap_year(year): if isinstance(year, int) is not True: raise TypeError("传入的参数不是整数") elif year == 0: raise ValueError("从公元一年开始") elif abs(year) != year: raise ValueError("传入的参数不是正整数") elif (year % 4 == 0 and year % 100 != 0) or year % 400 == 0: print("%d年是闰年" % year) return True else: print("%d年不是闰年" % year) return False class TestAssert(object): def test_exception_typeerror(self): with pytest.raises(Exception, match="从公元一年开始") as err_info: is_leap_year(0) assert err_info.type == ValueError if __name__ == '__main__': pytest.main()

3.setup和teardown

我们可以在函数、类或模块执行的前后设置执行固定的方法,即setup和teardown

3.1 函数级别

setup_function和teardown_function会在每个test开头的方法执行前后各执行一次

import pytest def setup_function(): print("setup_function(): 每个方法之前执行") def teardown_function(): print("teardown_function(): 每个方法之后执行") def test_01(): print("正在执行test1") def test_02(): print("正在执行test2") class TestClass(object): def test_03(self): print("正在执行 类方法 test3") def normal_func(): print("正在执行normal_func") if __name__ == '__main__': pytest.main(["-s"]) 执行打印结果: test_1.py setup_function(): 每个方法之前执行 正在执行test1 .teardown_function(): 每个方法之后执行 setup_function(): 每个方法之前执行 正在执行test2 .teardown_function(): 每个方法之后执行 正在执行 类方法 test3 .
3.2 类级别

setup_class 和 teardown_class 会在在类执行前后执行一次

import pytest class TestMethod(object): @classmethod def setup_class(cls): print("setup_class(self):每个类之前执行一次") @classmethod def teardown_class(cls): print("teardown_class(self):每个类之后执行一次") def setup_method(self): print("setup_method(self):在每个方法之前执行") def teardown_method(self): print("teardown_method(self):在每个方法之后执行\n") def test_01(self): print("正在执行test1") def test_02(self): print("正在执行test2") def normal_func(self): print("正在执行normal_func") if __name__ == '__main__': pytest.main(["-s"]) 输出打印结果: test_1.py setup_class(self):每个类之前执行一次 setup_method(self):在每个方法之前执行 正在执行test1 .teardown_method(self):在每个方法之后执行 setup_method(self):在每个方法之前执行 正在执行test2 .teardown_method(self):在每个方法之后执行 teardown_class(self):每个类之后执行一次
3.3 模块级别

setup_module 和 teardown_module 会模块内只执行一次,和函数级别,类级别不冲突

import pytest def setup_module(): print("setup_module():在模块最之前执行") def teardown_module(): print("teardown_module:在模块之后执行") def setup_function(): print("setup_function():每个方法之前执行") def teardown_function(): print("teardown_function():每个方法之后执行") def test_outside_1(): print('正在执行test_outside_1') def test_outside_2(): print('正在执行test_outside_2') class TestMethod(object): @classmethod def setup_class(cls): print("setup_class(self):每个类之前执行一次") @classmethod def teardown_class(cls): print("teardown_class(self):每个类之后执行一次") def setup_method(self): print("setup_method(self):在每个方法之前执行") def teardown_method(self): print("teardown_method(self):在每个方法之后执行\n") def test_01(self): print("正在执行test1") def test_02(self): print("正在执行test2") def normal_func(self): print("正在执行normal_func") if __name__ == '__main__': pytest.main(["-s"]) 输出打印结果: test_1.py setup_module():在模块最之前执行 setup_function():每个方法之前执行 正在执行test_outside_1 .teardown_function():每个方法之后执行 setup_function():每个方法之前执行 正在执行test_outside_2 .teardown_function():每个方法之后执行 setup_class(self):每个类之前执行一次 setup_method(self):在每个方法之前执行 正在执行test1 .teardown_method(self):在每个方法之后执行 setup_method(self):在每个方法之前执行 正在执行test2 .teardown_method(self):在每个方法之后执行 teardown_class(self):每个类之后执行一次 teardown_module:在模块之后执行

4.pytest.fixture

前面方法都是统一执行或不执行的,如果遇到某些方法需要执行另一些不需要执行的情况,可以使用fixture解决

4.1 fixture基本使用
import pytest import requests @pytest.fixture() def get_web_url(): print('get_web_url') return 'https://www.baidu.com' # 把被pytest.fixture装饰的函数名当做参数传入,得到的是该函数的return值 def test_web(get_web_url): print(get_web_url) r = requests.get(get_web_url) assert r.status_code == 200, '测试失败' if __name__ == '__main__': pytest.main(["-s"]) 执行打印结果: test_1.py get_web_url https://www.baidu.com .
4.2 共享fixture函数

如果想让某个fixture函数被其他模块使用,我们可以把它写到conftest.py文件里(文件名固定)共享

pytest_fixture/conftest.py

import pytest @pytest.fixture() def login_fixture(): print("\n公用的登陆方法")

使用方法1:当做参数 pytest_fixture/test_fixture1.py

import pytest def test_get_carts(): print("\n测试查询购物车,无需登录") class TestFixtures(object): def test_get_user_info(self, login_fixture): print("获取用户信息") def test_order_info(self, login_fixture): print("查询订单信息") def test_logout(login_fixture): print("退出登录") if __name__ == '__main__': pytest.main(['-s', 'test_fixture1.py']) 输出结果: test_fixture1.py 测试查询购物车,无需登录 . 公用的登陆方法 获取用户信息 . 公用的登陆方法 查询订单信息 . 公用的登陆方法 退出登录 .

使用方法2:装饰器 pytest_fixture/test_fixture2.py

import pytest def test_get_carts(): print("\n测试查询购物车,无需登录") @pytest.mark.usefixtures('login_fixture') class TestFixtures(object): def test_get_user_info(self): print("获取用户信息") def test_order_info(self): print("查询订单信息") @pytest.mark.usefixtures('login_fixture') def test_logout(): print("退出登录") if __name__ == '__main__': pytest.main(['-s', 'test_fixture2.py']) 输出结果: test_fixture2.py 测试查询购物车,无需登录 . 公用的登陆方法 获取用户信息 . 公用的登陆方法 查询订单信息 . 公用的登陆方法 退出登录 .
4.3 fixture_scope参数

@pytest.fixture(scope=‘function’),参数scope的值可以是function、class、module、package、session

作用域说明function默认值,每个测试用例都会执行一次class每个类都只执行一次(第一个)module每个module(py文件)都只执行一次(第一个)package每个包都只执行一次session每个session只执行一次 import pytest @pytest.fixture(scope='class') def foo(): print('foo') def test_1(foo): print('普通测试用例111111') def test_2(foo): print('普通测试用例22222') class TestClass(object): def test_one(self, foo): print('类实例方法测试用例111111') def test_two(self, foo): print('类实例方法测试用例22222') if __name__ == '__main__': pytest.main(['-s', 'test_2_scope.py']) 打印输出结果 test_2_scope.py foo 普通测试用例111111 .foo 普通测试用例22222 .foo 类实例方法测试用例111111 .类实例方法测试用例22222 .
4.4 fixture_params参数

@pytest.fixture(params=None) ,参数params的值是一个list。每个值都会自动遍历一次

import pytest @pytest.fixture(params=['admin', 'zhangsan', 'lisi']) def username(request): return request.param @pytest.fixture(params=['1234567', '12345678', '123456789']) def password(request): return request.param def test_check_regist(username, password): print(username, '==>', password) if __name__ == '__main__': pytest.main(['-s', 'test_2_scope.py']) 打印输出结果 test_2_scope.py admin ==> 1234567 .admin ==> 12345678 .admin ==> 123456789 .zhangsan ==> 1234567 .zhangsan ==> 12345678 .zhangsan ==> 123456789 .lisi ==> 1234567 .lisi ==> 12345678 .lisi ==> 123456789 .
4.5 fixture_autouse参数

该参数默认是False,即不会自动执行测试用例,当改为True的时候,当前运行的所有测试函数,即使不使用fixture装饰器也会自动调用

5.pytest.mark

除了使用fixture装饰器,我们还可以使用mark标记,用法类似,都是使用装饰器

装饰器说明pytest.mark.xfail()标记为预期失败pytest.mark.skip()无条件跳过执行pytest.mark.skipif()有条件跳过执行pytest.mark.parametrize()参数化Fixture方法pytest.mark.usefixtures()使用类、模块或项目中的Fixture方法
5.1 xfail 失败

如果你在测试某个功能的时候,就断定它是失败的(比如说开发未完成),那我们可以使用xfail进行标记(输出标记符号为x)

xfail(condition=True, reason=None, raises=None, run=True, strict=False)

import pytest class Test_ABC: def test_a(self): print("test_a") # 如果条件为 False,那么这个标记无意义 @pytest.mark.xfail(condition=False, reason="预期失败") def test_b(self): print("test_b") assert 0 @pytest.mark.xfail(condition=True, reason="预期失败") def test_c(self): print("test_c") assert 0 if __name__ == '__main__': pytest.main(['-s', 'test_3_mark.py']) 打印输出: test_3_mark.py test_a .test_b Ftest_c x
5.2 skip、skipif 跳过

如果是因为测试流程需要,测试的时候不想执行某个测试用例,我们可以通过skip标记来跳过(输出标记符号为s)

skip(reason=None) skipif(condition, reason=None)

import pytest class Test_ABC: def test_a(self): print("test_a") # 开启跳过标记 @pytest.mark.skip(reason="无条件跳过不执行,就是任性顽皮") def test_b(self): print("test_b") @pytest.mark.skipif(condition=1, reason="有条件跳过不执行,依旧任性顽皮") def test_c(self): print("test_c") if __name__ == '__main__': pytest.main(['-s', 'test_3_mark.py']) 打印输出: test_3_mark.py test_a .ss
5.3 parametrize 参数化

如果需要给测试用例传输参数的话可以使用parametrize标记,注意key和value的位置 parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)

import pytest class Test_ABC: def test_a(self): print("test_a") @pytest.mark.parametrize("a", [3, 6]) def test_b(self, a): print("test_b data:a=%s" % a) @pytest.mark.parametrize(["a", "b"], [(1, 2), (3, 4)]) def test_c(self, a, b): print("test_c a: %s; b: %s" % (a, b)) if __name__ == '__main__': pytest.main(['-s', 'test_3_mark.py']) 打印输出: test_3_mark.py test_a .test_b data:a=3 .test_b data:a=6 .test_c a: 1; b: 2 .test_c a: 3; b: 4 .

6.配置文件 pytest.ini

pytest是可以使用配置文件执行的,该配置文件名固定是pytest.ini,把它放到运行路径下即可

举个栗子

[pytest] addopts = -s test_12.py test_13.py testpaths = ./scripts python_files = test_*.py python_classes = Test_* python_functions = test_* ;在ini文件中注释语句是以分号开始的, 所有的注释语句不管多长都是独占一行直到结束的

addopts是指命令行参数 testpathsv是测试搜索的路径,一般是当前文件夹 python_files是测试搜索的文件,即以test开头的py文件 python_classes与python_functions意思同上,分别作用类和方法

执行方式 如果在pycharm直接右键运行,它可能会执行两次(配置文件也会执行一次),所以建议使用命令行执行,直接在配置文件执行pytest即可,它会自动找pytest.ini文件执行测试

7.常用插件

pytest的强大原因之一,就是它支持插件,有插件的支持让它变得强大和好用

7.1 pytest-html 测试报告

如果你想要在测试之后生成测试报告,可以使用pytest-html插件

pip install pytest-html

使用方法很简单,只需要在命令行执行的时候加上--html=./report.html参数,或直接写在配置文件里

pytest.ini

addopts = -s test_14.py --html=./report.html
7.2 pytest-ordering 执行顺序

我们知道测试用例的执行顺序是按照用例名按ASCII码顺序排序的,如果想要实现自定义顺序,我们可以使用

pip install pytest-ordering import pytest class Test_ABC: def setup_class(self): print("setup_class") def teardown_class(self): print("teardown_class") @pytest.mark.run(order=2) # order=2 后运行 def test_a(self): print("test_a") assert 1 @pytest.mark.run(order=1) # order=1 先运行 def test_b(self): print("test_b") assert 1 if __name__ == '__main__': pytest.main(["-s", "test_4_plugins.py"]) 打印输出: test_4_plugins.py setup_class test_b .test_a .teardown_class
7.3 pytest-rerunfailures 失败重试

如果在执行某个测试用例遇到的失败想要重试,我们可以使用pytest-rerunfailures插件指定重试次数

pip install pytest-rerunfailures

该插件的使用很简单,就是在命令行加上--reruns指定重试次数,也可以在配置文件里写

addopts = -s test_16.py --html=./report.html --reruns 2

补充:如果不想要某些插件生效,可以使用-p no:命令

[pytest] addopts = -s test_16.py -p no:ordering -p no:html -p no:rerunfailures
最新回复(0)