在Python 3.11版以下的版本中,assert_type
(源代码)不可用,如何通过unittest
TestCase
类Assert类型注解?
from typing import List
from unittest import TestCase
def dummy() -> List[str]:
return ["one", "two", "three"]
class TestDummy(TestCase):
def test_dummy(self):
self.assertEqual(
List[str],
type(dummy())
)
测试失败,输出如下:
<class 'list'> != typing.List[str]
Expected :typing.List[str]
Actual :<class 'list'>
<Click to see difference>
Traceback (most recent call last):
File "C:\Users\z\dev\mercata\scratch\mock_testing.py", line 12, in test_dummy
self.assertEqual(
AssertionError: typing.List[str] != <class 'list'>
我目前使用的方法如下:
data = dummy()
self.assertTrue(
type(data) == list
)
self.assertTrue(all([
type(d) == str for d in data
]))
这是可行的,但是需要迭代整个对象,这对于更大的数据集来说很难处理,对于Python 3.11以下的版本(不需要第三方包),有没有更有效的方法?
2条答案
按热度按时间oxf4rvwz1#
assert_type
用于请求一个静态类型检查器来确认一个值是某个类型。在正常运行时,这个方法不做任何事情。如果你想使用它,那么你应该使用静态分析工具,例如mypy或pyright。检查assertEqual
是一个运行时操作,并且不像某些语言,python中泛型的示例在运行时不保留它们的类型信息。这就是为什么类被显示为标准的<class 'list'>
,而不是来自方法类型注解的泛型。因为
assert_type
在运行时不执行任何操作,所以它不会检查实际列表的内容。它被用来向代码中添加显式类型检查,并且只有在所有关于变量构造方式的输入都经过了正确的类型检查时才有用。因此,它在单元测试中也没有用。例如,以下脚本只生成一个错误:
这检测到
dummy
返回的int列表的错误,但是assert_type
成功了,因为如果dummy
遵守了它的约定,它将是正确的。如果我们像下面这样修复dummy,那么此时我们会得到预期的
assert_type
错误:一个二个一个一个
w9apscun2#
虽然我同意评论者表达的一般观点,即这类事情可能应该留给静态类型检查器而不是单元测试,但出于学术目的,您可以构造自己的Assert,而无需太多努力。
list[str]
是泛型类型list
的指定版本。通过为list
添加下标,实际上是调用它的__class_getitem__
方法,该方法返回指定的类型。类型参数 * 实际上是 * 存储的,类型模块提供了get_args
/get_origin
函数,以便在运行时从泛型类型中提取更详细的类型信息。问题在于任何具体的
list
对象(比如["one", "two", "three"]
)都不存储任何关于它所包含的元素类型的信息(原因显而易见),这意味着在运行时,我们必须自己检查元素的类型。因此问题就变成了你希望你的检查有多迂腐。例如,列表可以是你想要的长度(或者你的内存允许的长度)。如果你有一个一百万个元素的列表对象,你真的想检查每一个元素吗?一个可能的折衷方案可能是只检查第一个元素的类型或者类似的东西。
下面是一个函数的例子,它检查仅由“常规”类型参数化的任意可迭代类型(即不是类似
list[tuple[int]]
的类型):还要注意的是,根据你实际传递给这个函数的可迭代对象的不同,(试图)消耗结果迭代器(通过
next
或all
)可能是一个糟糕的主意,你需要确保这不会产生任何不良的副作用。下面是一个演示:
要将其合并到
unittest.TestCase
中,可以执行以下操作:但同样,这很可能不是一个好主意,因为像
--strict
模式下的mypy
这样的东西在确保整个代码的类型安全方面可能比您希望在运行时做得更好。这意味着如果您声明def dummy() -> list[str]: ...
,但在函数体中您声明了return ["a", 1]
,那么mypy
将接收到它并向您大喊大叫。因此,则不需要这样的测试。