8.pytest强大的fixture(下)
fixtures参数化
fixture函数可以进行参数化的调用,这种情况下,相关测试集会被多次调用,即依赖该fixture的测试的集合。测试函数通常无需关注这种重复测试 .
fixture的参数化有助于为那些可以以多种方式配置的组件编写详尽的功能测试 .
扩展之前的示例,我们标记fixture来创建两个smtp_connection的实例,这会使得所有的测试使用这两个不同的fixture运行两次:
1 # conftest.py 2 import pytest 3 import smtplib 4 5 @pytest.fixture(scope="module", params=["smtp.qq.com", "mail.163.org"]) 6 def smtp_connection(request): 7 smtp_connection = smtplib.SMTP(request.param, 587, timeout=5) 8 yield smtp_connection 9 print("finalizing %s" % smtp_connection) 10 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实例运行了两次。
在参数化的fixture中使用marks
pytest.param()可以用来用来接收通过marks参数传入的标志,就像使用@pytest.mark.parametrize。 如下:
1 # test_fixture_marks.py 2 import pytest 3 4 @pytest.fixture(params=[0, 1, pytest.param(2, marks=pytest.mark.skip)]) 5 def data_set(request): 6 return request.param 7 8 def test_data(data_set): 9 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。这可以使得fixture的设计更容易模块化,并可以在多个项目中复用fixture .
扩展前面的例子作为一个简单的范例,我们在一个已经定义的smtp_connection中插入一个实例化的APP对象:
1 # test_appsetup.py 2 3 import pytest 4 5 class App(object): 6 def __init__(self, smtp_connection): 7 self.smtp_connection = smtp_connection 8 9 @pytest.fixture(scope="module") 10 def app(smtp_connection): 11 return App(smtp_connection) 12 13 def test_smtp_connection_exists(app): 14 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
重写fixtures
在大型的项目中,为了保持代码的可读性和可维护性,你可能需要重新在本地定义一个fixture来重写一个global或者root的fixture。
在文件夹(conftest)这一层重写
测试的文件结构如下:
1 tests/ 2 __init__.py 3 conftest.py 4 # tests/conftest.py 5 import pytest 6 @pytest.fixture 7 def username(): 8 return 'username' 9 10 test_something.py 11 # test/test_something.py 12 def test_username(username): 13 assert username == "username" 14 subfolder/ 15 __init__.py 16 conftest.py 17 # tests/subfolder/conftest.py 18 import pytest 19 @pytest.fixture 20 def username(username): 21 return 'overridden‐' + username 22 test_something.py 23 # tests/subfolder/test_something.py 24 def test_username(username): 25 assert username == 'overridden‐username'
如上所示,fixture可以通过使用同样的函数名来进行重写。
在module这一层重写
文件结构如下:
1 tests/ 2 __init__.py 3 conftest.py 4 # tests/conftest.py 5 import pytest 6 @pytest.fixture 7 def username(): 8 return 'username' 9 10 test_something.py 11 # test/test_something.py 12 import pytest 13 14 @pytest.fixture 15 def username(username): 16 return 'overridden‐' + username 17 18 def test_username(username): 19 assert username == "username" 20 test_something_else.py 21 # tests/test_something_else.py 22 import pytest 23 @pytest.fixture 24 def username(username): 25 return 'overridden‐else-' + username 26 27 def test_username(username): 28 assert username == "overridden‐else‐username"