Python 7.pytest 强大的 fixture (中)

Maple · 2020年12月28日 · 962 次阅读

scope:在类/模块/整个测试中共享 fixture 实例

当 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)

scope 越大,实例化越早

当函数调用多个 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 中处于最后一个

fixture 的调用结束/执行清理代码

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
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册