简单来说,pytest中的夹具就是用来实现测试前的环境准备,提供测试数据和测试后的环境清理动作。类似于unittest框架里的setup(前置处理),teardown(后置处理)。
常用在测试环境搭建和销毁,测试数据的共享,登录登出操作,数据库连接和关闭,浏览器打开关闭等操作。
新建 test_fixture.py 文件如下:
import pytest@pytest.fixture def myfixture(): print("this is myfixture") return True def test_one(myfixture): assert myfixture is True
运行命令:pytest test_fixture.py -vs
(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>pytest test_fixture.py -vs
=================================== test session starts ===================================
platform win32 -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- e:\programs\python\python38\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.8.8', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.3', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0', 'rerunfailures': '9.1.1', 'assume': '2.2.0', 'requests-mock': '1.7.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_271', 'foo': 'bar'}rootdir: C:\Users\057776\PycharmProjects\pytest-demo\testfixture
plugins: html-3.1.1, metadata-1.11.0, rerunfailures-9.1.1, assume-2.2.0, requests-mock-1.7.0
collected 1 itemtest_fixture.py::test_one this is myfixture PASSED
=================================== 1 passed in 0.03s ===================================
(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>
说明:
测试函数test_one 使用myfixture作为参数,pytest 运行时候,会先查找并运行它参数的同名夹具myfixture,打印出了"this is my fixture",并且捕获到了夹具的返回值 True。
夹具执行过程(官网说明):
在基本层面上,测试函数通过将它们声明为参数来请求它们所需的夹具。
当 pytest 开始运行测试时,它会查看该测试函数签名中的参数,然后搜索与这些参数具有相同名称的夹具。一旦 pytest 找到它们,它就会运行这些固定装置,捕获它们返回的内容(如果有的话),并将这些对象作为参数传递给测试函数。
使用@pytest.fixture装饰器可以将测试函数标记成夹具,可以在测试用例文件中定义夹具,也可以在conftest.py文件中定义夹具,推荐使用conftest.py文件定义夹具。
fixture它有5个参数,分别是scope, params, autouse, ids, name。
@
fixture
(fixture_function=None, *, scope='function', params=None, autouse=False, ids=None, name=None)
scope:
作用域参数,他有4个级别分别是 function,class,module,session,默认function。
params:
一个可选的参数列表,默认值为None,可以用来实现 参数化,参数化的时候这里需要用到一个参数request,使用“request.param”来接受请求参数列表,进而实现参数化。
ids:
字符串列表,与params配合使用,如果没有提供 id,它们将从参数中自动生成。
autouse:
默认值False ,需要显式引用来激活夹具。如果为TRUE,则所有测试函数都可以使用该夹具(慎用)。
name:
夹具名称默认取的是被定义成夹具的函数名称,可以修改这个参数来修改夹具名称,需要注意的是,只能使用修改后的夹具名,不能使用原来的夹具函数名。
夹具中可以使用return,yield关键字为测试函数提供值,推荐使用yield关键字,他们的区别如下:
新建conftest.py配置文件如下,定义一个function级别的夹具。
import pytest
@pytest.fixture(scope='function')
def myfixture_function():
print("开始加载function级别夹具")
yield 100
print("开始退出function级别夹具")
修改test_fixture.py文件如下:
def test_one(myfixture_function):
assert myfixture_function == 100
文件目录结构如下:
进入testfixture目录,运行命令:pytest -vs
Microsoft Windows [版本 10.0.19044.1889]
(c) Microsoft Corporation。保留所有权利。(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>pytest -vs
=================================== test session starts ===================================
platform win32 -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- e:\programs\python\python38\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.8.8', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.3', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0', 'rerunfailures': '9.1.1', 'assume': '2.2.0', 'requests-mock': '1.7.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_271', 'foo': 'bar'}rootdir: C:\Users\057776\PycharmProjects\pytest-demo\testfixture
plugins: html-3.1.1, metadata-1.11.0, rerunfailures-9.1.1, assume-2.2.0, requests-mock-1.7.0
collected 1 itemtest_fixture.py::test_one 开始加载function级别夹具
PASSED开始退出function级别夹具
=================================== 1 passed in 0.05s ===================================(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>
可知,function级别的夹具在显示调用该夹具的测试用例执行完后销毁。
语法糖:
pytest.mark.usefixtures(*args)
*args – The names of the fixture to use, as strings.
看到装饰器的参数就能晓得,usefixtures可以和一个或者多个fixture配合使用。
修改conftest.py配置文件如下,新增一个class级别的夹具。
import pytest
@pytest.fixture(scope='function')
def myfixture_function():
print("开始加载function级别夹具")
yield 100
print("开始退出function级别夹具")
@pytest.fixture(scope='class')
def myfixture_class():
print("开始加载class级别夹具")
yield 200
print("开始退出class级别夹具")
修改test_fixture.py文件如下:
import pytest
def test_one(myfixture_function):
assert myfixture_function == 100
@pytest.mark.usefixtures('myfixture_class')
class TestFixture:
def test_two(self, myfixture_function):
assert myfixture_function == 100
def test_three(self):
pass
进入testfixture目录,运行命令:pytest -vs
(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>pytest -vs
=================================== test session starts ===================================
platform win32 -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- e:\programs\python\python38\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.8.8', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.3', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0', 'rerunfailures': '9.1.1', 'assume': '2.2.0', 'requests-mock': '1.7.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_271', 'foo': 'bar'}rootdir: C:\Users\057776\PycharmProjects\pytest-demo\testfixture
plugins: html-3.1.1, metadata-1.11.0, rerunfailures-9.1.1, assume-2.2.0, requests-mock-1.7.0
collected 3 itemstest_fixture.py::test_one 开始加载function级别夹具
PASSED开始退出function级别夹具test_fixture.py::TestFixture::test_two 开始加载class级别夹具
开始加载function级别夹具
PASSED开始退出function级别夹具test_fixture.py::TestFixture::test_three PASSED开始退出class级别夹具
=================================== 3 passed in 0.07s ===================================(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>
可知,class级别的夹具在测试类中所有测试用例执行完后销毁。
修改conftest.py配置文件如下,新增一个module级别的夹具。
import pytest
@pytest.fixture(scope='function')
def myfixture_function():
print("开始加载function级别夹具")
yield 100
print("开始退出function级别夹具")
@pytest.fixture(scope='class')
def myfixture_class():
print("开始加载class级别夹具")
yield 200
print("开始退出class级别夹具")
@pytest.fixture(scope='module')
def myfixture_module():
print("开始加载module级别夹具")
yield 300
print("开始退出module级别夹具")
修改test_fixture.py文件如下:
import pytest
pytestmark = pytest.mark.usefixtures('myfixture_module')
def test_one(myfixture_function):
assert myfixture_function == 100
@pytest.mark.usefixtures('myfixture_class')
class TestFixture:
def test_two(self, myfixture_function):
assert myfixture_function == 100
def test_three(self):
pass
进入testfixture目录,运行命令:pytest -vs
(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>pytest -vs
=================================== test session starts ===================================
platform win32 -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- e:\programs\python\python38\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.8.8', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.3', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0', 'rerunfailures': '9.1.1', 'assume': '2.2.0', 'requests-mock': '1.7.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_271', 'foo': 'bar'}rootdir: C:\Users\057776\PycharmProjects\pytest-demo\testfixture
plugins: html-3.1.1, metadata-1.11.0, rerunfailures-9.1.1, assume-2.2.0, requests-mock-1.7.0
collected 3 itemstest_fixture.py::test_one 开始加载module级别夹具
开始加载function级别夹具
PASSED开始退出function级别夹具test_fixture.py::TestFixture::test_two 开始加载class级别夹具
开始加载function级别夹具
PASSED开始退出function级别夹具test_fixture.py::TestFixture::test_three PASSED开始退出class级别夹具
开始退出module级别夹具
=================================== 3 passed in 0.08s ===================================(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>
可知,module级别的夹具在测试文件中所有用例执行完成后销毁。
修改conftest.py配置文件如下,新增一个session级别的夹具。这里需要注意参数 autouse=True会话级别的夹具才能生效。
import pytest
@pytest.fixture(scope='function')
def myfixture_function():
print("开始加载function级别夹具")
yield 100
print("开始退出function级别夹具")
@pytest.fixture(scope='class')
def myfixture_class():
print("开始加载class级别夹具")
yield 200
print("开始退出class级别夹具")
@pytest.fixture(scope='module')
def myfixture_module():
print("开始加载module级别夹具")
yield 300
print("开始退出module级别夹具")
@pytest.fixture(scope='session', autouse=True)
def myfixture_session():
print("开始加载session级别夹具")
yield 400
print("开始退出session级别夹具")
在testfixture目录中新建test_sample.py文件如下:
def test_session(myfixture_function):
assert myfixture_function == 100
进入testfixture目录,运行命令:pytest -vs
(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>pytest -vs
=================================== test session starts ===================================
platform win32 -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- e:\programs\python\python38\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.8.8', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.3', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0', 'rerunfailures': '9.1.1', 'assume': '2.2.0', 'requests-mock': '1.7.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_271', 'foo': 'bar'}rootdir: C:\Users\057776\PycharmProjects\pytest-demo\testfixture
plugins: html-3.1.1, metadata-1.11.0, rerunfailures-9.1.1, assume-2.2.0, requests-mock-1.7.0
collected 4 itemstest_fixture.py::test_one 开始加载session级别夹具
开始加载module级别夹具
开始加载function级别夹具
PASSED开始退出function级别夹具test_fixture.py::TestFixture::test_two 开始加载class级别夹具
开始加载function级别夹具
PASSED开始退出function级别夹具test_fixture.py::TestFixture::test_three PASSED开始退出class级别夹具
开始退出module级别夹具test_sample.py::test_session 开始加载function级别夹具
PASSED开始退出function级别夹具
开始退出session级别夹具
=================================== 4 passed in 0.09s ===================================(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>
可知,session级别的夹具在pytest运行完所有测试用例后销毁。
params:一个可选的参数列表,默认值为None,可以用来实现 参数化,参数化的时候这里需要用到一个参数request,使用“request.param”来接受请求参数列表,进而实现参数化。
pytest 的最大优势之一是其极其灵活的夹具系统。它允许我们将复杂的测试需求归结为更简单和有组织的功能,我们只需要让每个功能描述它们所依赖的东西。
例如下面的例子,新建test_append.py文件,在夹具order中使用了夹具first_entry。
import pytest # Arrange @pytest.fixture def first_entry(): return "a" # Arrange @pytest.fixture def order(first_entry): return [first_entry] def test_string(order): # Act order.append("b") # Assert assert order == ["a", "b"]
夹具可以重复使用,这是pytest功能强大的另外一个原因。不同的测试函数可以请求相同的夹具,并让 pytest 为每个测试提供来自该夹具的自己的结果,这样夹具就可以提供一致的、可重复的结果给不同的测试函数使用。
例如下面的例子,修改test_append.py文件,2个测试函数test_string,test_int都使用了夹具order。
import pytest # Arrange @pytest.fixture def first_entry(): return "a" # Arrange @pytest.fixture def order(first_entry): return [first_entry] def test_string(order): # Act order.append("b") # Assert assert order == ["a", "b"] def test_int(order): # Act order.append(2) # Assert assert order == ["a", 2]
测试函数或者夹具中也可以使用多个夹具,例如下面的例子,修改test_append.py文件如下:
import pytest # Arrange @pytest.fixture def first_entry(): return "a" # Arrange @pytest.fixture def second_entry(): return 2 # Arrange @pytest.fixture def order(first_entry, second_entry): return [first_entry, second_entry] # Arrange @pytest.fixture def expected_list(): return ["a", 2, 3.0] def test_string(order, expected_list): # Act order.append(3.0) # Assert assert order == expected_list
request是给请求测试函数提供信息的特殊夹具。请求夹具,来自测试函数或者夹具函数。请求对象允许访问请求测试函数上下文,并且在间接参数化夹具的情况下具有可选的“param”属性。
import pytest
data = [1, 2, 3]
@pytest.fixture(params=data)
def myfixture(request):
return request.param, request.config
def test_one(myfixture):
print(myfixture)
reference:
API Reference — pytest documentation
How to use fixtures — pytest documentation