7.pytest强大的fixture(中)

533 阅读3分钟

公众号关注:测试充电宝,一起交流

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