如何使用 pytest 参数化测试的解包参数在 mypy 上输入注释?

How to make work typing annotation on mypy with unpacking params for pytest parameterized test?

我在 my_module.py 上有一个带有签名的简单方法:

def my_method(value: float, extra: str = "something") -> str:
    pass

然后我在 test_my_module.py 上对其进行了参数化测试:

from typing import List
from typing import Union

import pytest

import my_module

@pytest.mark.parametrize(
    "params, expected",
    [
        ([1.0], "result1"),
        ([2.0], "result2"),
        ([2.0, "extra"], "result3")
    ],
)
def test_my_method(params: List[Union[int, str]], expected: str) -> None:
    assert my_module.my_method(*params) == expected

当我 运行 动态类型测试时 typeguard 它通过了。但是 mypy 我得到这些 [arg-type] 错误:

tests/test_my_module.py: note: In function "test_my_method":
tests/test_my_module.py:50:30: error: Argument 1 to "my_method" has incompatible type "*List[Union[int, str]]"; expected "float" 
[arg-type]
        assert my_module.my_method(*params) == expected
                                   ^
tests/test_my_module.py:50:30: error: Argument 1 to "my_method" has incompatible type "*List[Union[int, str]]"; expected "str" 
[arg-type]
        assert my_module.my_method(*params) == expected
                                   ^

不太确定我应该如何注释这些类型以使其通过。请注意,这是一个最小的示例,因此我想在我创建的列表中解压缩参数列表 params 以参数化测试。

有什么想法吗?

您可以使用 Tuple 代替 List 进行注释:

@pytest.mark.parametrize(
    "params, expected",
    [
        ([1.0], "result1"),
        ([2.0], "result2"),
        ([2.0, "extra"], "result3")
    ],
)
def test_my_method(params: Union[Tuple[float], Tuple[float, str]], expected: str) -> None:
    assert my_module.my_method(*params) == expected

这保证了固定数量的元素,因此静态类型检查器可以推断出签名是兼容的。

我建议将变量参数表示为元组而不是列表。键入目前不允许列表在特定索引处具有特定类型,但是固定大小的元组可以为其维度指定类型

您当前拥有的两个形状要么是一个浮点数:Tuple[float],要么是一个浮点数和一个字符串:Tuple[float, str]——通过将您的参数化调用和参数转换为此,它应该可以很好地工作:

from typing import Tuple
from typing import Union

import pytest

import my_module

@pytest.mark.parametrize(
    "params, expected",
    [
        ((1.0,), "result1"),
        ((2.0,), "result2"),
        ((2.0, "extra"), "result3")
    ],
)
def test_my_method(params: Union[Tuple[float], Tuple[float, str]], expected: str) -> None:
    assert my_module.my_method(*params) == expected

拆分您的测试也可能有意义——您可能在这里过度使用了参数化,并且对您的固定参数进行一组测试而对您的可选参数进行另一组测试可能会更清楚(而不是试图将所有这些测试与参数化一起塞住)——也许是这样的:

@pytest.mark.parametrize(
    "arg, expected",
    [
        (1.0, "result1"),
        (2.0, "result2"),
    ],
)
def test_my_method_one_arg(arg: float, expected: str) -> None:
    assert my_module.my_method(arg) == expected


@pytest.mark.parametrize(
    "arg, extra, expected",
    [
        (1.0, "extra", "result3"),
        (2.0, "extra", "result4"),
    ],
)
def test_my_method_extra_arg(arg: float, extra: str, expected: str) -> None:
    assert my_module.my_method(arg, extra) == expected