自動(dòng)化測(cè)試中使用Pytest Fixture?推薦十種常見用法!
Pytest 是一個(gè)功能強(qiáng)大的 Python 測(cè)試框架,其中的Fixture 是 Pytest 中的一個(gè)重要功能。它允許你設(shè)置一些特定的測(cè)試環(huán)境或準(zhǔn)備測(cè)試數(shù)據(jù),這些環(huán)境和數(shù)據(jù)可以在多個(gè)測(cè)試用例中重復(fù)使用。通過使用fixture,你可以避免在每個(gè)測(cè)試函數(shù)中編寫重復(fù)的設(shè)置和清理代碼,使得測(cè)試更加干凈、簡潔,并提高代碼的可維護(hù)性。
本文將介紹 Pytest Fixture 的概念、用途以及十種常見的使用方法,并提供相應(yīng)的示例代碼。
1、什么是 Fixture?
Fixture 是 Pytest 中用于提供測(cè)試環(huán)境的一種機(jī)制,它可以在測(cè)試函數(shù)執(zhí)行前后進(jìn)行一些準(zhǔn)備工作和清理工作,如初始化數(shù)據(jù)庫連接、創(chuàng)建臨時(shí)文件等。Fixture 可以被多個(gè)測(cè)試用例共享使用,從而提高測(cè)試代碼的復(fù)用性和可維護(hù)性。
2、Fixture用途
fixture的主要用途包括:
- 設(shè)置測(cè)試環(huán)境:例如,配置數(shù)據(jù)庫連接、初始化外部服務(wù)等。
- 準(zhǔn)備測(cè)試數(shù)據(jù):提供測(cè)試所需的數(shù)據(jù),如用戶信息、產(chǎn)品列表等。
- 模擬外部依賴:當(dāng)測(cè)試難以直接訪問外部系統(tǒng)時(shí),可以使用fixture來模擬這些系統(tǒng)的行為。
- 執(zhí)行特定操作:在測(cè)試前后執(zhí)行某些特定操作,如臨時(shí)修改配置、記錄日志等。
- 共享資源:在不同的測(cè)試用例之間共享資源,減少資源的創(chuàng)建和銷毀開銷。
3、10種常見用法及示例
基礎(chǔ)使用
import pytest
@pytest.fixture
def sample_data():
return [1, 2, 3]
def test_example(sample_data):
assert sum(sample_data) == 6
帶參數(shù)的fixture
import pytest
@pytest.fixture(params=[1, 2, 3])
def number(request):
return request.param
def test_number(number):
assert number in [1, 2, 3]
使用范圍(scope)
在pytest中,fixture的作用域決定了測(cè)試夾具的生命周期以及它們能被哪些測(cè)試用例或測(cè)試類訪問。以下是pytest中fixture的幾種作用域及其用途:
- function:這是fixture的默認(rèn)作用域。當(dāng)不指定scope參數(shù)時(shí),fixture會(huì)在每個(gè)測(cè)試函數(shù)執(zhí)行前被調(diào)用,并在測(cè)試函數(shù)結(jié)束后清理。它適用于需要為每個(gè)測(cè)試準(zhǔn)備和清理資源的場(chǎng)合。例如,打開和關(guān)閉數(shù)據(jù)庫連接、初始化和釋放內(nèi)存空間等。
- class:當(dāng)設(shè)置scope='class'時(shí),fixture會(huì)在一個(gè)測(cè)試類開始前被調(diào)用一次,然后在整個(gè)類的所有測(cè)試方法運(yùn)行完畢后被清理。這適用于整個(gè)測(cè)試類共享的準(zhǔn)備工作,如創(chuàng)建共享的測(cè)試環(huán)境或?qū)ο蟆?/li>
- module:如果設(shè)置scope='module',則fixture會(huì)在整個(gè)模塊的第一個(gè)測(cè)試開始前被調(diào)用,并在模塊中的所有測(cè)試完成后被清理。這通常用于模塊級(jí)別的資源管理,比如建立和斷開與外部服務(wù)的連接。
- package/package.module:當(dāng)設(shè)置為scope='package'或scope='package.module'時(shí),fixture將在整個(gè)包或指定的包的模塊中運(yùn)行一次。這適用于跨模塊或跨包共享的測(cè)試資源,例如配置全局服務(wù)或執(zhí)行一次性的環(huán)境設(shè)置。
- session:通過scope='session'設(shè)置,fixture將在整個(gè)測(cè)試會(huì)話中只運(yùn)行一次。這適用于開銷較大,且所有測(cè)試用例都可以共享的準(zhǔn)備步驟,如復(fù)雜的系統(tǒng)級(jí)設(shè)置或一次性的資源分配。
- instance:如果設(shè)置了scope='instance',則可以為每個(gè)測(cè)試實(shí)例創(chuàng)建一個(gè)fixture實(shí)例。這允許在不同的測(cè)試用例之間共享狀態(tài),而不需要在每個(gè)測(cè)試用例中重新準(zhǔn)備。
- classinstance:通過scope='classinstance',可以為每個(gè)測(cè)試類創(chuàng)建一個(gè)fixture實(shí)例。與instance類似,但適用于在類的不同方法間共享狀態(tài)。
- once:使用scope='once'時(shí),fixture只會(huì)被調(diào)用一次,無論被多少個(gè)測(cè)試用例或測(cè)試類引用。這對(duì)于單例資源管理或確保某些操作只執(zhí)行一次非常有用。
例如:
import pytest
@pytest.fixture(scope="class")
def class_fixture():
print("setup")
yield "data"
print("teardown")
def test_use_fixture(class_fixture):
assert class_fixture == "data"
通過合理選擇不同的fixture作用域,可以有效地組織和管理測(cè)試代碼,提高測(cè)試的效率和可維護(hù)性。
fixture的依賴
可以指定一個(gè)fixture依賴于其他fixture。
import pytest
@pytest.fixture
def db():
return "sqlite:///:memory:"
@pytest.fixture
def session(db):
return create_session(db)
def test_database(session):
assert isinstance(session, Session)
示例2:
import pytest
@pytest.fixture
def login():
user = User()
user.login()
yield user
user.logout()
@pytest.fixture
def profile(login):
return login.get_profile()
使用autouse自動(dòng)應(yīng)用
通過設(shè)置autouse=True,無需手動(dòng)將fixture作為參數(shù)傳遞到測(cè)試用例中。
import pytest
@pytest.fixture(autouse=True)
def print_hello():
print("Hello, World!")
def test_example():
pass
使用request對(duì)象訪問fixture
request對(duì)象可以用來訪問調(diào)用的fixture及其參數(shù)。
import pytest
@pytest.fixture(params=[1, 2, 3])
def numbers(request):
return request.param * 2
def test_numbers(numbers):
assert numbers % 2 == 0
異常處理
可以對(duì)fixture中的異常進(jìn)行處理。
import pytest
@pytest.fixture(autouse=True)
def exception_handler():
try:
yield "some setup code"
except Exception as e:
print(f"Handled exception: {e}")
raise e
def test_example():
raise ValueError("test error")
使用indirect間接引用
indirect 參數(shù)是 Pytest 中 Fixture 的一個(gè)高級(jí)用法,在pytest中,indirect參數(shù)用于間接引用fixture。
indirect=True 是 @pytest.mark.parametrize 裝飾器的一個(gè)可選參數(shù)。當(dāng)設(shè)置為 True 時(shí),它告訴 pytest,對(duì)應(yīng)的參數(shù)值不是一個(gè)直接的輸入值,而是一個(gè)用于請(qǐng)求 fixture 的名稱。這意味著,pytest 會(huì)查找一個(gè)與參數(shù)值同名的 fixture,并使用該 fixture 的返回值作為測(cè)試用例的參數(shù)。
當(dāng)使用indirect時(shí),它允許你通過一個(gè)fixture的名稱來引用另一個(gè)fixture,而不是直接使用它的返回值。這在某些情況下非常有用,比如當(dāng)你需要將一個(gè)fixture的返回值作為另一個(gè)fixture的輸入。
使用方法
- 在測(cè)試函數(shù)的參數(shù)列表中指定需要間接引用的 Fixture 名稱。
- 在 @pytest.mark.parametrize 裝飾器中使用 indirect=True 參數(shù)來啟用間接引用。
示例1:
import pytest
@pytest.fixture
def test_data(request):
# 這里只是一個(gè)簡單的示例,你可以根據(jù)需要生成更復(fù)雜的測(cè)試數(shù)據(jù)
data = request.param
return data * 2
然后,我們編寫一個(gè)測(cè)試用例,并使用 @pytest.mark.parametrize 裝飾器來參數(shù)化它。注意,我們?cè)?indirect=True 時(shí)傳遞 fixture 名稱 test_data,而不是直接的測(cè)試數(shù)據(jù)值:
def test_example(test_data):
assert test_data > 0
最后,我們使用 @pytest.mark.parametrize 來指定測(cè)試數(shù)據(jù)的范圍,并將 indirect 設(shè)置為 True:
@pytest.mark.parametrize("test_data", [1, 2, 3], indirect=True)
def test_example(test_data):
assert test_data > 0
在這個(gè)例子中,pytest 會(huì)為每一組測(cè)試數(shù)據(jù)(1, 2, 3)調(diào)用 test_data fixture,并將 fixture 的返回值(即數(shù)據(jù)的兩倍)作為 test_example 測(cè)試用例的參數(shù)。因此,test_example 測(cè)試用例實(shí)際上會(huì)運(yùn)行三次,每次使用不同的參數(shù)值(2, 4, 6)。
通過這種方式,你可以使用 fixtures 來生成復(fù)雜的測(cè)試數(shù)據(jù),并通過 @pytest.mark.parametrize 和 indirect=True 來參數(shù)化你的測(cè)試用例。
示例2:indirect間接引用fixture另外一種用法:
import pytest
# 定義一個(gè)fixture,返回一個(gè)字符串
@pytest.fixture
def string_fixture():
return "Hello, World!"
# 定義另一個(gè)fixture,接受一個(gè)字符串作為參數(shù),并返回其長度
@pytest.fixture
def length_fixture(request):
string = request.getfixturevalue("string_fixture")
return len(string)
# 使用indirect間接引用length_fixture,并將結(jié)果傳遞給test_example測(cè)試函數(shù)
def test_example(length_fixture):
assert length_fixture == 13
在上面的示例中,我們定義了兩個(gè)fixture:string_fixture和length_fixture。string_fixture返回一個(gè)字符串,而length_fixture接受一個(gè)字符串作為參數(shù),并返回其長度。
在test_example測(cè)試函數(shù)中,我們使用indirect間接引用了length_fixture,并將其結(jié)果傳遞給測(cè)試函數(shù)。這樣,pytest會(huì)自動(dòng)解析length_fixture的依賴關(guān)系,并獲取string_fixture的返回值作為輸入。
運(yùn)行上述代碼,將會(huì)執(zhí)行test_example測(cè)試函數(shù),并斷言字符串的長度是否為13。由于使用了indirect間接引用,我們可以靈活地管理fixture之間的依賴關(guān)系,并在測(cè)試中使用它們的結(jié)果。
使用fixtures獲取所有fixtures
可以獲取當(dāng)前測(cè)試用例的所有fixtures。
import pytest
@pytest.fixture(scope="module")
def module_fixture():
return "module data"
def test_example(module_fixture, request):
fixtures = request.getfixturevalue("module_fixture")
assert fixtures == "module data"
自定義fixture解析器
可以自定義解析器來控制如何解析fixture的名稱。
import pytest
from pytest_mock import MockerFixture
@pytest.fixture(scope="module", autouse=True)
def my_custom_parser(request, mocker: MockerFixture):
mocker.patch("my_module.some_function", return_value="mocked value")
request.addfinalizer(lambda: mocker.stop())
以上是pytest fixture的10種常見用法及示例,它們涵蓋了從基本使用到高級(jí)技巧的各個(gè)方面。掌握這些用法可以幫助你編寫更加高效和易于維護(hù)的測(cè)試代碼。