fixture 函数可以进行参数化的调用,这种情况下,相关测试集会被多次调用,即依赖该 fixture 的测试的集合。测试函数通常无需关注这种重复测试 .
fixture 的参数化有助于为那些可以以多种方式配置的组件编写详尽的功能测试 .
扩展之前的示例,我们标记 fixture 来创建两个 smtp_connection 的实例,这会使得所有的测试使用这两个不同的 fixture 运行两次:
# conftest.py
import pytest
import smtplib
@pytest.fixture(scope="module", params=["smtp.qq.com", "mail.163.org"])
def smtp_connection(request):
smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
yield smtp_connection
print("finalizing %s" % smtp_connection)
smtp_connection.close()
相对于之前的代码,这里主要的改动就是为@pytest.fixture定义了一个 params,params 是一个可以通过 request.params 在 fixture 中进行访问的列表。无需修改其他的代码,让我们来运行它:
(pytest) D:\study\auto-pytest>pytest test_module.py
================================== test session starts ==================================
platform win32 -- Python 3.7.1, pytest-6.0.2, py-1.9.0, pluggy-0.13.1
rootdir: D:\study\auto-pytest
collected 4 items
test_module.py FFFF [100%]
================================== FAILURES ==================================
__________________________________ test_ehlo[smtp.qq.com0] __________________________________
smtp_connection = <smtplib.SMTP object at 0x0000025BBB5D7EF0>
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
> assert b"smtp.qq.com" in msg
E AssertionError: assert b'smtp.qq.com' in b'newxmesmtplogicsvrszb5.qq.com\nPIPELINING\nSIZE 73400320\nSTARTTLS\nAUTH LOGIN PLAIN\nAUTH=LOGIN\nMAILCOMPRESS\n8BITMIME'
test_module.py:4: AssertionError
__________________________________ test_noop[smtp.qq.com0] __________________________________
smtp_connection = <smtplib.SMTP object at 0x0000025BBB5D7EF0>
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
assert response == 250
> assert 0 # for debug
E assert 0
test_module.py:11: AssertionError
__________________________________ test_ehlo[smtp.qq.com1] __________________________________
smtp_connection = <smtplib.SMTP object at 0x0000025BBB5D7EF0>
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
> assert b"smtp.qq.com" in msg
E AssertionError: assert b'smtp.qq.com' in b'newxmesmtplogicsvrszb5.qq.com\nPIPELINING\nSIZE 73400320\nSTARTTLS\nAUTH LOGIN PLAIN\nAUTH=LOGIN\nMAILCOMPRESS\n8BITMIME'
test_module.py:4: AssertionError
__________________________________ test_noop[smtp.qq.com1] __________________________________
smtp_connection = <smtplib.SMTP object at 0x0000025BBB5D7EF0>
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
assert response == 250
> assert 0 # for debug
E assert 0
test_module.py:11: AssertionError
--------------------------------- Captured stdout teardown ---------------------------------
finalizing <smtplib.SMTP object at 0x0000025BBB5D7EF0>
================================== short test summary info ==================================
FAILED test_module.py::test_ehlo[smtp.qq.com0] - AssertionError: assert b'smtp.qq.com' in b'newxmesmtplogicsvrszb5.qq.com\nPIPELINING\nSIZE 73400320\nSTARTTLS\nAUTH LOGIN PLAIN\nAU...
FAILED test_module.py::test_noop[smtp.qq.com0] - assert 0
FAILED test_module.py::test_ehlo[smtp.qq.com1] - AssertionError: assert b'smtp.qq.com' in b'newxmesmtplogicsvrszb5.qq.com\nPIPELINING\nSIZE 73400320\nSTARTTLS\nAUTH LOGIN PLAIN\nAU...
FAILED test_module.py::test_noop[smtp.qq.com1] - assert 0
================================== 4 failed in 0.31s ==================================
可以看到每个测试函数都是用不同的 smtp_connection 实例运行了两次。
pytest.param() 可以用来用来接收通过 marks 参数传入的标志,就像使用@pytest.mark.parametrize。 如下:
# test_fixture_marks.py
import pytest
@pytest.fixture(params=[0, 1, pytest.param(2, marks=pytest.mark.skip)])
def data_set(request):
return request.param
def test_data(data_set):
pass
运行该测试会跳过 data_set 中值为 2 的调用:
(pytest) D:\study\auto-pytest>pytest test_fixture_marks.py
==================================== test session starts ====================================
platform win32 -- Python 3.7.1, pytest-6.0.2, py-1.9.0, pluggy-0.13.1
rootdir: D:\study\auto-pytest
collected 3 items
test_fixture_marks.py ..s [100%]
==================================== 2 passed, 1 skipped in 0.14s ====================================
不仅测试函数可以使用 fixture,fixture 函数本身也可以使用其他的 fixture。这可以使得 fixture 的设计更容易模块化,并可以在多个项目中复用 fixture .
扩展前面的例子作为一个简单的范例,我们在一个已经定义的 smtp_connection 中插入一个实例化的 APP 对象:
# test_appsetup.py
import pytest
class App(object):
def __init__(self, smtp_connection):
self.smtp_connection = smtp_connection
@pytest.fixture(scope="module")
def app(smtp_connection):
return App(smtp_connection)
def test_smtp_connection_exists(app):
assert app.smtp_connection
这里我们定义了一个名为 app 的 fixture 并且接收之前定义的 smtp_connection 的 fixture,在其中实例化了一个 App 对象。运行结果如下
(pytest) D:\study\auto-pytest>pytest -v test_appsetup.py
======================================== test session starts ========================================
platform win32 -- Python 3.7.1, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- d:\envs\pytest\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\study\auto-pytest
collected 2 items
test_appsetup.py::test_smtp_connection_exists[smtp.qq.com0] PASSED [ 50%]
test_appsetup.py::test_smtp_connection_exists[smtp.qq.com1] PASSED [100%]
======================================== 2 passed in 0.17s ========================================
因为对 smtp_connection 做了参数化,测试用例将会使用两个不同的 App 实例分别运行来连接各自的 smtp 服务器。App fixture 无需关心 smtp_connection 的参数化,pytest 会自动的分析其中的依赖关系
注意,app fixture 声明了作用域是 module,并使用了同样是 module 作用域的 smtp_connection。 如果 smtp_connection 是 session 的作用域,这个示例依然是有效的:fixture 可以引用作用域更广泛的 fixture,但是反过来不行,比如 session 作用域的 fixture 不能引用一个 module 作用域的 fixture
在大型的项目中,为了保持代码的可读性和可维护性,你可能需要重新在本地定义一个 fixture 来重写一个 global 或者 root 的 fixture。
测试的文件结构如下:
tests/
__init__.py
conftest.py
# tests/conftest.py
import pytest
@pytest.fixture
def username():
return 'username'
test_something.py
# test/test_something.py
def test_username(username):
assert username == "username"
subfolder/
__init__.py
conftest.py
# tests/subfolder/conftest.py
import pytest
@pytest.fixture
def username(username):
return 'overridden‐' + username
test_something.py
# tests/subfolder/test_something.py
def test_username(username):
assert username == 'overridden‐username'
如上所示,fixture 可以通过使用同样的函数名来进行重写。
文件结构如下:
tests/
__init__.py
conftest.py
# tests/conftest.py
import pytest
@pytest.fixture
def username():
return 'username'
test_something.py
# test/test_something.py
import pytest
@pytest.fixture
def username(username):
return 'overridden‐' + username
def test_username(username):
assert username == "username"
test_something_else.py
# tests/test_something_else.py
import pytest
@pytest.fixture
def username(username):
return 'overridden‐else-' + username
def test_username(username):
assert username == "overridden‐else‐username"