7.pytest 強大的 fixture (中)

Maple發表於2020-12-28

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

相關文章