在 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 装饰器,无需做任何特殊的事情。
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 测试中引入类型提示增强了清晰度,改进了调试和维护,并确保了类型安全。这些实践造就了一个健壮、可读且易于维护的测试套件,能够更好地应对未来的变更,同时将错误风险降至最低。