当 fixture 需要访问网络时,因为依赖于网络状况,通常是一个非常耗时的动作 。
扩展下上面的示例,我们可以将 scope="module"参数添加到@pytest.fixture中,这样每个测试模块就只会调用一次 smtp_connection 的 fixture 函数 (默认情况下时每个测试函数都调用一次)。因此,一个测试模块中的多个测试函数将使用同样的 smtp_connection 实例,从而节省了反复创建的时间。
scope 可能的值为:function, class, module, package 和 session
下面的示例将 fixture 函数放在独立的 conftest.py 中,这样可以在多个测试模块中访问使用该测试 fixture:
# conftest.py
import pytest
import smtplib
@pytest.fixture(scope="module")
def smtp_connection():
return smtplib.SMTP("smtp.qq.com", 587, timeout=5)
fixture 的名称依然为 smtp_connection,你可以在任意的测试用例中通过该名称来调用该 fixture(在 conftest.py 所在的目录及子目录下)
# test_module.py
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250 assert b"smtp.qq.com" in msg
assert 0 # for debug
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
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 实例,你可以按照如下来定义:
@pytest.fixture(scope="session")
def smtp_connection():
#该固件会在所有的用例中共享
scope 定义为 class 的话会创建一个在每个 class 中调用一次的 fixture
注意: Pytest 对于每个 fixture 只会缓存一个实例,这意味着如果使用参数化的 fixture,pytest 可能会比定义的作用域更多次的调用 fixture 函数(因为需要创建不同参数的 fixture)
当函数调用多个 fixtures 的时候,scope 较大的 (比如 session) 实例化早于 scope 较小的 (比如 function 或者 class)。同样 scope 的顺序则按照其在测试函数中定义的顺序及依赖关系来实例化。
@pytest.fixture(scope="session")
def s1():
pass
@pytest.fixture(scope="module")
def m1():
pass
@pytest.fixture
def f1(tmpdir):
pass
@pytest.fixture
def f2():
pass
def test_foo(f1, m1, f2, s1):
...
该函数所请求的 fixtures 的实例化顺序如下:
s1: 具有最大的 scope(session)
m1: 第二高的 scope(module)
tmpdir: f1 需要使用该 fixture,需要在 f1 之前实例化
f1:在 function 级的 scope 的 fixtures 中,在 test_foo 中处于第一个
f2:在 function 级的 scope 的 fixtures 中,在 test_foo 中处于最后一个
pytest 支持在 fixture 退出作用域的时候执行相关的清理/结束代码。使用 yield 而不是 return 关键字的时候,yield 后面的语句将会在 fixture 退出作用域的时候被调用来清理测试用例
# conftest.py
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp_connection():
smtp_connection = smtplib.SMTP("smtp.qq.com", 587, timeout=5)
yield smtp_connection
print("teardown smtp")
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:
@pytest.fixture(scope="module")
def smtp_connection():
with smtplib.SMTP("smtp.qq.com", 587, timeout=5) as smtp_connection:
yield smtp_connection