pytest 中的类型标注¶
为什么要为测试添加类型标注?¶
为测试添加类型标注提供了显著优势
可读性: 清晰定义预期的输入和输出,提高了可读性,尤其是在复杂或参数化的测试中。
重构: 这是为测试添加类型标注的主要好处,因为它将极大地帮助重构,让类型检查器指出生产代码和测试中所需的更改,而无需运行完整的测试套件。
对于生产代码,类型标注也有助于捕获一些可能根本无法通过测试(无论覆盖率如何)捕获的 bug,例如:
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%,但并未捕获到 bug(当然,这个例子“很明显”,但旨在说明问题)。
在测试套件中使用类型标注¶
要在 pytest 中为 fixture 添加类型标注,只需为 fixture 函数添加常规类型即可——仅仅因为使用了 fixture
装饰器,无需做任何特殊的事情。
import pytest
@pytest.fixture
def sample_fixture() -> int:
return 38
同样地,传递给测试函数的 fixture 需要用 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
当为接收其他 fixture 的 fixture 函数添加类型标注时,也适用相同的逻辑。
@pytest.fixture
def mock_env_user(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("USER", "TestingUser")
总结¶
将类型标注纳入 pytest 测试可以增强**清晰度**,改进**调试**和**维护**,并确保**类型安全**。这些实践将带来一个**健壮**、**可读**且**易于维护**的测试套件,能够更好地应对未来的变更,并将错误风险降至最低。