8.pytest 強大的 fixture (下)

Maple發表於2020-12-29

fixtures引數化

fixture函式可以進行引數化的呼叫,這種情況下,相關測試集會被多次呼叫,即依賴該fixture的測試的集合。測試函式通常無需關注這種重複測試 .

fixture的引數化有助於為那些可以以多種方式配置的元件編寫詳盡的功能測試 .

擴充套件之前的示例,我們標記fixture來建立兩個smtp_connection的例項,這會使得所有的測試使用這兩個不同的fixture執行兩次:

# conftest.py 
import pytest
import smtplib

@pytest.fixture(scope="module", params=["smtp.qq.com", "mail.163.org"])
def smtp_connection(request):
smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
yield smtp_connection
print("finalizing %s" % smtp_connection)
smtp_connection.close()

相對於之前的程式碼,這裡主要的改動就是為@pytest.fixture定義了一個params,params是一個可以通過request.params在fixture中進行訪問的列表。無需修改其他的程式碼,讓我們來執行它:

(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 4 items

test_module.py FFFF [100%]

================================== FAILURES ==================================
__________________________________ test_ehlo[smtp.qq.com0] __________________________________

smtp_connection = <smtplib.SMTP object at 0x0000025BBB5D7EF0>

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'newxmesmtplogicsvrszb5.qq.com\nPIPELINING\nSIZE 73400320\nSTARTTLS\nAUTH LOGIN PLAIN\nAUTH=LOGIN\nMAILCOMPRESS\n8BITMIME'

test_module.py:4: AssertionError
__________________________________ test_noop[smtp.qq.com0] __________________________________

smtp_connection = <smtplib.SMTP object at 0x0000025BBB5D7EF0>

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
__________________________________ test_ehlo[smtp.qq.com1] __________________________________

smtp_connection = <smtplib.SMTP object at 0x0000025BBB5D7EF0>

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'newxmesmtplogicsvrszb5.qq.com\nPIPELINING\nSIZE 73400320\nSTARTTLS\nAUTH LOGIN PLAIN\nAUTH=LOGIN\nMAILCOMPRESS\n8BITMIME'

test_module.py:4: AssertionError
__________________________________ test_noop[smtp.qq.com1] __________________________________

smtp_connection = <smtplib.SMTP object at 0x0000025BBB5D7EF0>

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
--------------------------------- Captured stdout teardown ---------------------------------
finalizing <smtplib.SMTP object at 0x0000025BBB5D7EF0>
================================== short test summary info ==================================
FAILED test_module.py::test_ehlo[smtp.qq.com0] - AssertionError: assert b'smtp.qq.com' in b'newxmesmtplogicsvrszb5.qq.com\nPIPELINING\nSIZE 73400320\nSTARTTLS\nAUTH LOGIN PLAIN\nAU...

FAILED test_module.py::test_noop[smtp.qq.com0] - assert 0
FAILED test_module.py::test_ehlo[smtp.qq.com1] - AssertionError: assert b'smtp.qq.com' in b'newxmesmtplogicsvrszb5.qq.com\nPIPELINING\nSIZE 73400320\nSTARTTLS\nAUTH LOGIN PLAIN\nAU...

FAILED test_module.py::test_noop[smtp.qq.com1] - assert 0
================================== 4 failed in 0.31s ==================================

可以看到每個測試函式都是用不同的smtp_connection例項執行了兩次。

在引數化的fixture中使用marks

pytest.param()可以用來用來接收通過marks引數傳入的標誌,就像使用@pytest.mark.parametrize。 如下:

# test_fixture_marks.py 
import pytest

@pytest.fixture(params=[0, 1, pytest.param(2, marks=pytest.mark.skip)])
def data_set(request):
return request.param

def test_data(data_set):
pass

執行該測試會跳過data_set中值為2的呼叫:

(pytest) D:\study\auto-pytest>pytest test_fixture_marks.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 3 items

test_fixture_marks.py ..s [100%]

==================================== 2 passed, 1 skipped in 0.14s ====================================

模組化: 通過fixture函式使用fixture

不僅測試函式可以使用fixture,fixture函式本身也可以使用其他的fixture。這可以使得fixture的設計更容易模組化,並可以在多個專案中複用fixture .

擴充套件前面的例子作為一個簡單的範例,我們在一個已經定義的smtp_connection中插入一個例項化的APP物件:

# test_appsetup.py 

import pytest

class App(object):
def __init__(self, smtp_connection):
self.smtp_connection = smtp_connection

@pytest.fixture(scope="module")
def app(smtp_connection):
return App(smtp_connection)

def test_smtp_connection_exists(app):
assert app.smtp_connection

這裡我們定義了一個名為app的fixture並且接收之前定義的smtp_connection的fixture,在其中例項化了一個App物件。執行結果如下

(pytest) D:\study\auto-pytest>pytest -v test_appsetup.py
======================================== test session starts ========================================
platform win32 -- Python 3.7.1, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- d:\envs\pytest\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\study\auto-pytest
collected 2 items

test_appsetup.py::test_smtp_connection_exists[smtp.qq.com0] PASSED [ 50%]
test_appsetup.py::test_smtp_connection_exists[smtp.qq.com1] PASSED [100%]

======================================== 2 passed in 0.17s ========================================

因為對smtp_connection做了引數化,測試用例將會使用兩個不同的App例項分別執行來連線各自的smtp伺服器。App fixture無需關心smtp_connection的引數化,pytest會自動的分析其中的依賴關係

注意,app fixture宣告瞭作用域是module,並使用了同樣是module作用域的smtp_connection。 如果smtp_connection是session的作用域,這個示例依然是有效的:fixture可以引用作用域更廣泛的fixture,但是反過來不行,比如session作用域的fixture不能引用一個module作用域的fixture

重寫fixtures

在大型的專案中,為了保持程式碼的可讀性和可維護性,你可能需要重新在本地定義一個fixture來重寫一個global或者root的fixture。

在資料夾(conftest)這一層重寫

測試的檔案結構如下:

tests/
__init__.py
conftest.py
# tests/conftest.py
import pytest
@pytest.fixture
def username():
return 'username'

test_something.py
# test/test_something.py
def test_username(username):
assert username == "username"
subfolder/
__init__.py
conftest.py
# tests/subfolder/conftest.py
import pytest
@pytest.fixture
def username(username):
return 'overridden‐' + username
test_something.py
# tests/subfolder/test_something.py
def test_username(username):
assert username == 'overriddenusername'

如上所示,fixture可以通過使用同樣的函式名來進行重寫。

在module這一層重寫

檔案結構如下:

tests/
__init__.py
conftest.py
# tests/conftest.py
import pytest
@pytest.fixture
def username():
return 'username'

test_something.py
# test/test_something.py
import pytest

@pytest.fixture
def username(username):
return 'overridden‐' + username

def test_username(username):
assert username == "username"
test_something_else.py
# tests/test_something_else.py
import pytest
@pytest.fixture
def username(username):
return 'overriddenelse-' + username

def test_username(username):
assert username == "overridden‐else‐username"

相關文章