Typing in 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_fixtureint 类型的参数。

同样的逻辑适用于 @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 测试中可以增强清晰度,改进调试维护,并确保类型安全。 这些实践造就了一个健壮的可读的易于维护的测试套件,该套件能更好地应对未来的变化,并将错误风险降到最低。