7.pytest强大的fixture(中)
scope:在类/模块/整个测试中共享fixture实例
当fixture需要访问网络时,因为依赖于网络状况,通常是一个非常耗时的动作 。
扩展下上面的示例,我们可以将scope="module"参数添加到@pytest.fixture中,这样每个测试模块就只会调用一次smtp_connection的fixture函数(默认情况下时每个测试函数都调用一次)。因此,一个测试模块中的多个测试函数将使用同样的smtp_connection实例,从而节省了反复创建的时间。
scope可能的值为:function, class, module, package 和 session
下面的示例将fixture函数放在独立的conftest.py中,这样可以在多个测试模块中访问使用该测试fixture:
1 # conftest.py 2 import pytest 3 import smtplib 4 5 @pytest.fixture(scope="module") 6 def smtp_connection(): 7 return smtplib.SMTP("smtp.qq.com", 587, timeout=5)
fixture的名称依然为smtp_connection,你可以在任意的测试用例中通过该名称来调用该fixture(在conftest.py所在的目录及子目录下)
1 # test_module.py 2 3 def test_ehlo(smtp_connection): 4 response, msg = smtp_connection.ehlo() 5 assert response == 250 assert b"smtp.qq.com" in msg 6 assert 0 # for debug 7 8 def test_noop(smtp_connection): 9 response, msg = smtp_connection.noop() 10 assert response == 250 assert 0 # for debug
我们故意添加了assert 0的断言来查看测试用例的运行情况:
(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 2 items test_module.py FF [100%] ======================= FAILURES ======================= _______________________ test_ehlo _______________________ smtp_connection = <smtplib.SMTP object at 0x000001BDA449BB70> 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'newxmesmtplogicsvrsza5.qq.com\nPIPELINING\nSIZE 73400320\nSTARTTLS\nAUTH LOGIN PLAIN\nAUTH=LOGIN\nMAILCOMPRESS\n8BITMIME' test_module.py:4: AssertionError _______________________ test_noop _______________________ smtp_connection = <smtplib.SMTP object at 0x000001BDA449BB70> 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 ======================= short test summary info ======================= FAILED test_module.py::test_ehlo - AssertionError: assert b'smtp.qq.com' in b'newxmesmtplogicsvrsza5.qq.com\nPIPELINING\nSIZE 73400320\nSTARTTLS\nAUTH LOGIN PLAIN\nAUTH=LOGIN\nMAIL... FAILED test_module.py::test_noop - assert 0 ======================= 2 failed in 0.32s =======================
可以看到这两个用例都失败了,并且你可以在traceback中看到smtp_connection被传进了这两个测试函数中。这两个函数复用了同一个smtp_connection实例如果你需要一个session作用域的smtp_connection实例,你可以按照如下来定义:
1 @pytest.fixture(scope="session") 2 def smtp_connection(): 3 #该固件会在所有的用例中共享
scope定义为class的话会创建一个在每个class中调用一次的fixture
注意: Pytest对于每个fixture只会缓存一个实例,这意味着如果使用参数化的fixture,pytest可能会比定义的作用域更多次的调用fixture函数(因为需要创建不同参数的fixture)
scope越大,实例化越早
当函数调用多个fixtures的时候,scope较大的(比如session)实例化早于scope较小的(比如function或者class)。同样scope的顺序则按照其在测试函数中定义的顺序及依赖关系来实例化。
1 @pytest.fixture(scope="session") 2 def s1(): 3 pass 4 5 @pytest.fixture(scope="module") 6 def m1(): 7 pass 8 9 @pytest.fixture 10 def f1(tmpdir): 11 pass 12 13 @pytest.fixture 14 def f2(): 15 pass 16 17 def test_foo(f1, m1, f2, s1): 18 ...
该函数所请求的fixtures的实例化顺序如下:
-
s1: 具有最大的scope(session)
-
m1: 第二高的scope(module)
-
tmpdir: f1需要使用该fixture,需要在f1之前实例化
-
f1:在function级的scope的fixtures中,在test_foo中处于第一个
-
f2:在function级的scope的fixtures中,在test_foo中处于最后一个
fixture的调用结束/执行清理代码
pytest支持在fixture退出作用域的时候执行相关的清理/结束代码。使用yield而不是return关键字的时候,yield后面的语句将会在fixture退出作用域的时候被调用来清理测试用例
1 # conftest.py 2 import smtplib 3 import pytest 4 @pytest.fixture(scope="module") 5 def smtp_connection(): 6 smtp_connection = smtplib.SMTP("smtp.qq.com", 587, timeout=5) 7 yield smtp_connection 8 print("teardown smtp") 9 smtp_connection.close()
无论测试是否发生了异常,print及smtp.close()语句将在module的最后一个测试函数完成之后被执行
$ pytest ‐s ‐q ‐‐tb=no FFteardown smtp 2 failed in 0.12 seconds
可以看到在两个测试函数完成后smtp_connection实例调用了相关的代码。注意如果我们定义scope为function级别(scope=‘function’),该部分代码会在每个测试函数结束后都会调用。测试函数本身并不需要关心fixture的实现的细节。
我们也可以在with语句中使用yield:
1 @pytest.fixture(scope="module") 2 def smtp_connection(): 3 with smtplib.SMTP("smtp.qq.com", 587, timeout=5) as smtp_connection: 4 yield smtp_connection