pytest-2.3:fixture/funcarg 演進的理由

目標受眾:閱讀此文件需要具備 python 測試、xUnit 設定方法和(以前的)基本 pytest funcarg 機制的基礎知識,請參閱 funcargs 和 pytest_funcarg__。如果您是 pytest 的新手,則可以忽略此部分,直接閱讀其他部分。

先前 pytest_funcarg__ 機制的缺點

在 pytest-2.3 之前的 funcarg 機制會在每次測試函數需要 funcarg 時呼叫工廠。如果工廠想要在不同範圍內重複使用資源,則通常會使用 request.cached_setup() 輔助函數來管理資源快取。以下是我們如何實作每個工作階段資料庫物件的基本範例

# content of conftest.py
class Database:
    def __init__(self):
        print("database instance created")

    def destroy(self):
        print("database instance destroyed")


def pytest_funcarg__db(request):
    return request.cached_setup(
        setup=DataBase, teardown=lambda db: db.destroy, scope="session"
    )

這種方法有幾個限制和困難

  1. 設定 funcarg 資源的範圍並不簡單,反而必須了解複雜的 cached_setup() 方法機制。

  2. 設定「db」資源的參數並不簡單:您需要套用「參數化」裝飾器或實作 pytest_generate_tests 鉤子,呼叫 parametrize(),在使用資源的地方執行參數化。此外,您需要修改工廠,使用包含 request.paramextrakey 參數,呼叫 Request.cached_setup

  3. 多個參數化的工作階段範圍資源會同時作用,讓它們難以影響受測應用程式的全域狀態。

  4. 您無法在 xUnit 設定方法中使用 funcarg 工廠。

  5. 如果未在測試函數簽章中宣告,非參數化的固定函數無法使用參數化的 funcarg 資源。

pytest-2.3 和其改良的 固定機制 解決了所有這些限制。

固定/funcarg 工廠的直接範圍

您可以使用 @pytest.fixture 裝飾器,直接宣告範圍,而不是使用快取範圍呼叫 cached_setup()

@pytest.fixture(scope="session")
def db(request):
    # factory will only be invoked once per session -
    db = DataBase()
    request.addfinalizer(db.destroy)  # destroy when session is finished
    return db

此工廠實作不再需要呼叫 cached_setup(),因為它只會在每個工作階段呼叫一次。此外,request.addfinalizer() 會根據工廠函式運作的指定資源範圍註冊一個終結器。

funcarg 資源工廠的直接參數化

先前,funcarg 工廠無法直接造成參數化。您需要在測試函式上指定 @parametrize 裝飾器,或實作 pytest_generate_tests 鉤子來執行參數化,亦即以不同的值集合多次呼叫測試。pytest-2.3 為工廠本身引入了裝飾器

@pytest.fixture(params=["mysql", "pg"])
def db(request): ...  # use request.param

在此,工廠將被呼叫兩次(分別將「mysql」和「pg」值設定為 request.param 屬性),而所有需要「db」的測試也會執行兩次。「mysql」和「pg」值也會用於報告測試呼叫變異。

這種參數化 funcarg 工廠的新方法在許多情況下允許重複使用已寫入的工廠,因為實際上 request.param 已在透過 metafunc.parametrize(indirect=True) 呼叫參數化測試函式/類別時使用。

當然,參數化和範圍設定結合起來是完全沒問題的

@pytest.fixture(scope="session", params=["mysql", "pg"])
def db(request):
    if request.param == "mysql":
        db = MySQL()
    elif request.param == "pg":
        db = PG()
    request.addfinalizer(db.destroy)  # destroy when session is finished
    return db

這會執行所有需要每個工作階段「db」資源的測試兩次,接收工廠函式兩個各自呼叫所建立的值。

使用 @fixture 裝飾器時沒有 pytest_funcarg__ 前綴

使用 @fixture 裝飾器時,函式的名稱表示資源可作為函式引數存取的名稱

@pytest.fixture()
def db(request): ...

可要求 funcarg 資源的名稱是 db

您仍然可以使用「舊」的非裝飾器方式來指定 funcarg 工廠,又稱

def pytest_funcarg__db(request): ...

但這樣就無法定義範圍設定和參數化。因此建議使用工廠裝飾器。

解決每個工作階段設定/autouse 固定裝置

pytest 長期提供 pytest_configure 和 pytest_sessionstart 鉤子,它們經常被用於設定全域資源。這會產生幾個問題

  1. 在分散式測試中,管理程序會設定永遠不需要的測試資源,因為它只協調工作程序的測試執行活動。

  2. 如果你僅執行收集(使用「–collect-only」),資源設定仍會執行。

  3. 如果 pytest_sessionstart 包含在某些子目錄 conftest.py 檔案中,它將不會被呼叫。這是因為此掛勾實際上用於報告,特別是包含平台/自訂資訊的測試標頭。

此外,除了實作 pytest_runtest_setup() 掛勾並自行處理範圍/快取之外,很難從外掛程式或 conftest 檔案定義範圍設定。而且,由於 pytest_runtest_setup() 是在測試執行期間呼叫,而參數化發生在收集時間,因此幾乎不可能使用參數化來執行此操作。

因此,pytest_configure/session/runtest_setup 通常不適合用於實作常見的固定元件需求。因此,pytest-2.3 引入了 自動使用固定元件(不需要請求的固定元件),它與一般 固定元件機制 完全整合,並淘汰了許多先前使用 pytest 掛勾的方式。

函數參數/固定元件偵測現在在收集時間發生

從 pytest-2.3 開始,固定元件/函數參數工廠的偵測在收集時間處理。這對於大型測試套件來說更有效率。此外,呼叫「pytest –collect-only」應能在未來顯示許多設定資訊,因此提供一個不錯的方法來瞭解專案中的固定元件管理概況。

結論和相容性注意事項

函數參數最初是在 pytest-2.0 中引入的。在 pytest-2.3 中,此機制已擴充和改良,現在稱為固定元件

  • 先前函數參數工廠使用特殊 pytest_funcarg__NAME 前置詞來指定,而不是使用 @pytest.fixture 裝飾器。

  • 工廠收到一個 request 物件,它透過 request.cached_setup() 呼叫來管理快取,並允許透過 request.getfuncargvalue() 呼叫來使用其他函數參數。這些複雜的 API 讓正確的參數化和實作資源快取變得困難。新的 pytest.fixture() 裝飾器允許宣告範圍,並讓 pytest 為你找出問題。

  • 如果您使用參數化和 funcarg 工廠,而這些工廠使用 request.cached_setup(),建議花幾分鐘時間簡化您的固定函數程式碼,改用 固定參照 裝飾器。這也會讓您利用測試的自動化每個資源分組。