我正在考虑在我的测试中启用与生产代码中相同的
mypy
规则。我开始在 mypy
操场上做一些修补,发现一些我不太明白 mypy
和 MagicMock
如何一起玩的事情。假设我有以下课程:
class A:
def method(self, variable: int) -> None:
print("Hey!")
现在,我想在另一个测试中创建该类的模拟,所以我会这样做:
mock = MagicMock(spec_set=A)
因为如果我尝试做类似的事情,我已经使用了
spec_set
:
mock.not_existing.return_value = "Meh"
然后我会得到一个运行时错误,但令人惊讶的是(?)我不会从
mypy
得到任何错误。这让我觉得也许 mypy 只是忽略了与 MagicMock
相关的所有内容。然后我尝试了类似的事情:
def function(some: A) -> None:
print(some)
# no mypy error either
function(mock)
再次,它不会触发任何
mypy
错误,我猜这是预期的行为,因为该模拟正在嘲笑 A
。尽管如此,如果你将其更改为:
class B:
def method_2(self, variable: int) -> None:
print("Hey!")
mock_2 = MagicMock(spec_set=B)
function(mock_2)
然后,也没有
mypy
错误。我还尝试了以下方法:
mock: A = MagicMock(spec_set=A)
mock.not_existing.something() # runtime and mypy error
mock.method.return_value = 1 # mypy error due to return_value not existing
那么,有什么办法可以改变这种行为呢?如果没有这个,我不确定在我的测试中添加
mypy
是否有意义,因为我猜我会得到假阴性。
作为问题的奖励部分,我也想到了这一点:
mock.method.return_value = "Meh"
这不会触发任何错误(运行时或来自
mypy
)。理想情况下,它应该抛出两者,因为我违反了规范(所以我猜 spec_set
应该对此做点什么),而且因为我没有返回 None
,正如类型提示所暗示的那样。
mypy
游乐场测试相同的示例
来自typeshed:
# We subclass with "Any" because mocks are explicitly designed to stand in for other types,
# something that can't be expressed with our static type system.
class NonCallableMock(Base, Any):
类型检查器查看此存根文件,将
Any
视为 NonCallableMock
的超类,并有效禁用此类所有实例的大多数类型检查。其中包括 MagicMock
的实例,它是 NonCallableMock
的后代。