自動化測試中使用Pytest Fixture?推薦十種常見用法!
Pytest 是一個功能強大的 Python 測試框架,其中的Fixture 是 Pytest 中的一個重要功能。它允許你設置一些特定的測試環境或準備測試數據,這些環境和數據可以在多個測試用例中重復使用。通過使用fixture,你可以避免在每個測試函數中編寫重復的設置和清理代碼,使得測試更加干凈、簡潔,并提高代碼的可維護性。
本文將介紹 Pytest Fixture 的概念、用途以及十種常見的使用方法,并提供相應的示例代碼。
1、什么是 Fixture?
Fixture 是 Pytest 中用于提供測試環境的一種機制,它可以在測試函數執行前后進行一些準備工作和清理工作,如初始化數據庫連接、創建臨時文件等。Fixture 可以被多個測試用例共享使用,從而提高測試代碼的復用性和可維護性。
2、Fixture用途
fixture的主要用途包括:
- 設置測試環境:例如,配置數據庫連接、初始化外部服務等。
- 準備測試數據:提供測試所需的數據,如用戶信息、產品列表等。
- 模擬外部依賴:當測試難以直接訪問外部系統時,可以使用fixture來模擬這些系統的行為。
- 執行特定操作:在測試前后執行某些特定操作,如臨時修改配置、記錄日志等。
- 共享資源:在不同的測試用例之間共享資源,減少資源的創建和銷毀開銷。
3、10種常見用法及示例
基礎使用
import pytest
@pytest.fixture
def sample_data():
return [1, 2, 3]
def test_example(sample_data):
assert sum(sample_data) == 6
帶參數的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的作用域決定了測試夾具的生命周期以及它們能被哪些測試用例或測試類訪問。以下是pytest中fixture的幾種作用域及其用途:
- function:這是fixture的默認作用域。當不指定scope參數時,fixture會在每個測試函數執行前被調用,并在測試函數結束后清理。它適用于需要為每個測試準備和清理資源的場合。例如,打開和關閉數據庫連接、初始化和釋放內存空間等。
- class:當設置scope='class'時,fixture會在一個測試類開始前被調用一次,然后在整個類的所有測試方法運行完畢后被清理。這適用于整個測試類共享的準備工作,如創建共享的測試環境或對象。
- module:如果設置scope='module',則fixture會在整個模塊的第一個測試開始前被調用,并在模塊中的所有測試完成后被清理。這通常用于模塊級別的資源管理,比如建立和斷開與外部服務的連接。
- package/package.module:當設置為scope='package'或scope='package.module'時,fixture將在整個包或指定的包的模塊中運行一次。這適用于跨模塊或跨包共享的測試資源,例如配置全局服務或執行一次性的環境設置。
- session:通過scope='session'設置,fixture將在整個測試會話中只運行一次。這適用于開銷較大,且所有測試用例都可以共享的準備步驟,如復雜的系統級設置或一次性的資源分配。
- instance:如果設置了scope='instance',則可以為每個測試實例創建一個fixture實例。這允許在不同的測試用例之間共享狀態,而不需要在每個測試用例中重新準備。
- classinstance:通過scope='classinstance',可以為每個測試類創建一個fixture實例。與instance類似,但適用于在類的不同方法間共享狀態。
- once:使用scope='once'時,fixture只會被調用一次,無論被多少個測試用例或測試類引用。這對于單例資源管理或確保某些操作只執行一次非常有用。
例如:
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作用域,可以有效地組織和管理測試代碼,提高測試的效率和可維護性。
fixture的依賴
可以指定一個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自動應用
通過設置autouse=True,無需手動將fixture作為參數傳遞到測試用例中。
import pytest
@pytest.fixture(autouse=True)
def print_hello():
print("Hello, World!")
def test_example():
pass
使用request對象訪問fixture
request對象可以用來訪問調用的fixture及其參數。
import pytest
@pytest.fixture(params=[1, 2, 3])
def numbers(request):
return request.param * 2
def test_numbers(numbers):
assert numbers % 2 == 0
異常處理
可以對fixture中的異常進行處理。
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 參數是 Pytest 中 Fixture 的一個高級用法,在pytest中,indirect參數用于間接引用fixture。
indirect=True 是 @pytest.mark.parametrize 裝飾器的一個可選參數。當設置為 True 時,它告訴 pytest,對應的參數值不是一個直接的輸入值,而是一個用于請求 fixture 的名稱。這意味著,pytest 會查找一個與參數值同名的 fixture,并使用該 fixture 的返回值作為測試用例的參數。
當使用indirect時,它允許你通過一個fixture的名稱來引用另一個fixture,而不是直接使用它的返回值。這在某些情況下非常有用,比如當你需要將一個fixture的返回值作為另一個fixture的輸入。
使用方法
- 在測試函數的參數列表中指定需要間接引用的 Fixture 名稱。
- 在 @pytest.mark.parametrize 裝飾器中使用 indirect=True 參數來啟用間接引用。
示例1:
import pytest
@pytest.fixture
def test_data(request):
# 這里只是一個簡單的示例,你可以根據需要生成更復雜的測試數據
data = request.param
return data * 2
然后,我們編寫一個測試用例,并使用 @pytest.mark.parametrize 裝飾器來參數化它。注意,我們在 indirect=True 時傳遞 fixture 名稱 test_data,而不是直接的測試數據值:
def test_example(test_data):
assert test_data > 0
最后,我們使用 @pytest.mark.parametrize 來指定測試數據的范圍,并將 indirect 設置為 True:
@pytest.mark.parametrize("test_data", [1, 2, 3], indirect=True)
def test_example(test_data):
assert test_data > 0
在這個例子中,pytest 會為每一組測試數據(1, 2, 3)調用 test_data fixture,并將 fixture 的返回值(即數據的兩倍)作為 test_example 測試用例的參數。因此,test_example 測試用例實際上會運行三次,每次使用不同的參數值(2, 4, 6)。
通過這種方式,你可以使用 fixtures 來生成復雜的測試數據,并通過 @pytest.mark.parametrize 和 indirect=True 來參數化你的測試用例。
示例2:indirect間接引用fixture另外一種用法:
import pytest
# 定義一個fixture,返回一個字符串
@pytest.fixture
def string_fixture():
return "Hello, World!"
# 定義另一個fixture,接受一個字符串作為參數,并返回其長度
@pytest.fixture
def length_fixture(request):
string = request.getfixturevalue("string_fixture")
return len(string)
# 使用indirect間接引用length_fixture,并將結果傳遞給test_example測試函數
def test_example(length_fixture):
assert length_fixture == 13
在上面的示例中,我們定義了兩個fixture:string_fixture和length_fixture。string_fixture返回一個字符串,而length_fixture接受一個字符串作為參數,并返回其長度。
在test_example測試函數中,我們使用indirect間接引用了length_fixture,并將其結果傳遞給測試函數。這樣,pytest會自動解析length_fixture的依賴關系,并獲取string_fixture的返回值作為輸入。
運行上述代碼,將會執行test_example測試函數,并斷言字符串的長度是否為13。由于使用了indirect間接引用,我們可以靈活地管理fixture之間的依賴關系,并在測試中使用它們的結果。
使用fixtures獲取所有fixtures
可以獲取當前測試用例的所有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種常見用法及示例,它們涵蓋了從基本使用到高級技巧的各個方面。掌握這些用法可以幫助你編寫更加高效和易于維護的測試代碼。