前面简单介绍了如何使用pytest, 感觉介绍得太泛泛了。个人感觉,pytest的精髓在fixture. 学pytest就不得不说fixture,fixture是pytest的精髓所在,就像unittest中的setup和teardown一样,如果不学fixture那么使用pytest和使用unittest是没什么区别的(个人理解)。
1.做测试前后的初始化设置,如测试数据准备,链接数据库,打开浏览器等这些操作都可以使用fixture来实现
2.测试用例的前置条件可以使用fixture实现
3.支持经典的xunit fixture ,像unittest使用的setup和teardown
4.fixture可以实现unittest不能实现的功能,比如unittest中的测试用例和测试用例之间是无法传递参数和数据的,但是fixture却可以解决这个问题。
下面,我们来看看它的功能: 有时候仅仅使用一组数据是无法充分测试函数功能的,参数化测试允许传递多组数据。
import pytest
@pytest.mark.parametrize("task",
[Task("sleep", done=True),
Task("wake", "brian"),
Task("breathe", "BRIAN", True),
Task("exercise", "BrIaN", "False")])
def test_add_2(task):
"""Demonstrate paramertrize with one parameter"""
task_id = task.add(task)
t_from_db = tasks.get(task_id)
assert equivalent(t_from_db, task)
@pytest.mark.parametrize() 的第一个参数是用逗号分隔的字符串列表;第二个参数是一个值列表。
pytest会轮流对每个task做测试,并分别报告每一个测试用例的结果。
以下是 多组键值对情况
import pytest
@pytest.mark.parametrize("str",
["abc","def","twq","tre"])
def test_add_2(str):
"""Demonstrate paramertrize with one parameter"""
str2 = "abc"
assert str == str2
执行如下:
(venv) C:\Users\admin\Desktop\ch2>pytest test_add_variety.py -v
=============================== test session starts ===============================
platform win32 -- Python 3.6.8, pytest-6.0.1, py-1.9.0, pluggy-0.13.1 -- c:\users\admin\desktop\ch2\venv\scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\admin\Desktop\ch2
collected 4 items
test_add_variety.py::test_add_2[abc] PASSED [ 25%]
test_add_variety.py::test_add_2[def] FAILED [ 50%]
test_add_variety.py::test_add_2[twq] FAILED [ 75%]
test_add_variety.py::test_add_2[tre] FAILED [100%]
fixture 可以放在单独的测试文件里。此时只有这个测试文件能够使用相关的fixture。
如果希望多个测试文件共享 fixture,可以在某个公共目录下新建一个 conftest.py 文件,将 fixture 放在其中。(作用域根据所放的文件夹决定,最上层文件夹的话整个项目共用,子文件夹的话,子文件夹里面的测试共用。)
尽管 conftest.py 是Python 模块,但它不能被测试文件导入。import conftest 的用法是不允许出现的。conftest.py 被 pytest 视作一个本地插件库。可以把 tests/conftest.py 看成是一个供 tests 目录下所有测试使用的 fixture仓库。
fixture 函数会在测试函数之前运行,但如果 fixture 函数包含 yield,那么系统会在 yield 处停止,转而运行测试函数,等测试函数执行完毕后再回到 fixture,继续执行 yield 之后的代码。
可以将 yield 之前的代码视为 配置(setup)过程,将yield 之后的代码视为清理(teardown)过程。
无论测试过程中发生了说明,yield之后的代码都会被执行
直接运行测试,则不会看到fisture的执行过程。
如果希望看到测试过程中执行的是什么,以及执行的先后顺序。pytest 提供的 --setup-show 选项可以实现这个功能。
pytest --setup-show test_add.py
fixture 非常适合存放测试数据,并且它可以返回任何数据。
import pytest
@pytest.fixture()
def a_tuple():
return (1, "foo", None, {"bar": 23})
def test_a_tuple(a_tuple):
assert a_tuple[3]["bar"] == 32
yeild 返回数据
import pytest
@pytest.fixture()
def a_tuple():
print("1111")
yield (1, "foo", None, {"bar": 23})
print("2222")
def test_a_tuple(a_tuple):
assert a_tuple[3]["bar"] == 32
明显23不等于32,所以会失败。
fixture互相调用
import pytest
@pytest.fixture()
def a_tuple():
return (1,2,3)
@pytest.fixture()
def two_tuple(a_tuple):
if a_tuple[2] == 3:
return (1, "foo", None, {"bar": 23})
return False
def test_a_tuple(two_tuple):
if two_tuple:
assert two_tuple[3]["bar"] == 23
用例中传入多个fixture
import pytest
@pytest.fixture()
def a_tuple():
return 1
@pytest.fixture()
def two_tuple():
return 2
def test_a_tuple(a_tuple, two_tuple):
assert a_tuple == 1
assert two_tuple == 2
fixture的范围 fixture 包含一个叫 scope(作用范围)的可选参数,用于控制 fixture 执行配置和销毁逻辑的频率。@pytest.fixture() 的 scope 参数有四个待选值:
function class module session(默认值) 以下是对各个 scope 的概述
scope=“function”
函数级别的 fixture 每个测试函数只需要运行一次。配置代码在测试用例运行之前运行,销毁代码在测试用例运行之后运行。是默认值
scope=“class”
类级别的fixture 每个测试类只需要运行一次,无论测试类里面有多少类方法都可以共享这个fixture
scope="module"
模块级别的fixture每个模块只需要运行一次,无论模块里有多少个测试函数、类方法或其他fixture 都可以共享这个fixture
scope=“session”
会话级别的 fixture 每次会话只需要运行一次。一次 pytest 会话中所有测试函数、方法都可以共享这个 fixture。
之前用到的 fixture 都是根据测试本身来命名的(或者针对示例的测试类使用 usefixtures)。我们可以通过制定 autouse=True选项,使作用域内的测试函数都自动运行 fixture
@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
pytest.mark 除了使用fixture装饰器,我们还可以使用mark标记,用法类似,都是使用装饰器
装饰器 说明 pytest.mark.xfail() 标记为预期失败 pytest.mark.skip() 无条件跳过执行 pytest.mark.skipif() 有条件跳过执行 pytest.mark.parametrize() 参数化Fixture方法 pytest.mark.usefixtures() 使用类、模块或项目中的Fixture方法 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
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
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
.
配置文件 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文件执行测试
常用插件 pytest的强大原因之一,就是它支持插件,有插件的支持让它变得强大和好用
pytest-html 测试报告 如果你想要在测试之后生成测试报告,可以使用pytest-html插件
pip install pytest-html
使用方法很简单,只需要在命令行执行的时候加上--html=./report.html参数,或直接写在配置文件里
pytest.ini
addopts = -s test_14.py --html=./report.html
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
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