在 pytest 中使用類型標註

注意

本頁假設讀者熟悉 Python 的類型系統及其優點。

欲了解更多資訊,請參閱Python 的類型標註文件

為何要為測試加上類型標註?

為測試加上類型標註提供了顯著的優勢

  • 可讀性: 清晰地定義了預期的輸入和輸出,提高了可讀性,尤其是在複雜或參數化的測試中。

  • 重構: 這是為測試加上類型標註的主要好處,因為它將極大地幫助重構,讓類型檢查器指出生產代碼和測試中必要的更改,而無需運行完整的測試套件。

對於生產代碼,類型標註也有助於捕獲一些可能根本無法被測試捕獲的錯誤(無論覆蓋率如何),例如

def get_caption(target: int, items: list[tuple[int, str]]) -> str:
    for value, caption in items:
        if value == target:
            return caption

類型檢查器會正確地指出函數可能返回 None,然而即使是完整覆蓋率的測試套件也可能遺漏這種情況

def test_get_caption() -> None:
    assert get_caption(10, [(1, "foo"), (10, "bar")]) == "bar"

請注意,上面的代碼具有 100% 的覆蓋率,但沒有捕獲到錯誤(當然這個例子是「顯而易見的」,但旨在說明這一點)。

在測試套件中使用類型標註

要在 pytest 中為 fixtures 添加類型標註,只需將常規類型添加到 fixture 函數即可 – 不需要因為 fixture 裝飾器而做任何特殊的事情。

import pytest


@pytest.fixture
def sample_fixture() -> int:
    return 38

同樣地,傳遞給測試函數的 fixtures 需要使用 fixture 的返回類型進行註釋

def test_sample_fixture(sample_fixture: int) -> None:
    assert sample_fixture == 38

從類型檢查器的角度來看,sample_fixture 實際上是由 pytest 管理的 fixture 並不重要,重要的是 sample_fixture 是一個 int 類型的參數。

相同的邏輯適用於 @pytest.mark.parametrize

@pytest.mark.parametrize("input_value, expected_output", [(1, 2), (5, 6), (10, 11)])
def test_increment(input_value: int, expected_output: int) -> None:
    assert input_value + 1 == expected_output

當為接收其他 fixtures 的 fixture 函數添加類型標註時,也適用相同的邏輯

@pytest.fixture
def mock_env_user(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setenv("USER", "TestingUser")

結論

將類型標註納入 pytest 測試中,可增強清晰度,改進除錯維護,並確保類型安全。 這些實踐有助於建立一個穩健可讀易於維護的測試套件,使其能夠更好地應對未來的變化,並將錯誤風險降至最低。