首页 > 解决方案 > Autospec 模拟没有正确保留签名?

问题描述

根据模拟指南

自动指定创建的模拟对象与它们要替换的对象具有相同的属性和方法,并且任何函数和方法(包括构造函数)都具有与真实对象相同的调用签名。

但这似乎不是真的。Stdlibinspect仍然在 mock 上看到一个通用*args, **kwargs签名:

>>> from unittest.mock import patch
>>> def foo(arg1, arg2, arg3):
...     pass
...     
>>> print(*inspect.signature(foo).parameters)
arg1 arg2 arg3
>>> with patch("__main__.foo", autospec=True) as mock:
...     print(*inspect.signature(mock).parameters)
...     
args kwargs

autospec 确实有效,因为它mock(1,2,3,4)会正确 raise TypeError: too many positional arguments,但似乎这是通过调用堆栈中更深的一些代码实现的。它不是通过调用签名完成的。

在您实际依赖签名本身的代码中(并且在测试中模拟时需要保留正确的签名),如何以正确保留签名的方式自动指定模拟?

标签: pythonunit-testingmockinginspect

解决方案


这实际上被认为是 Python 中的一个错误,并已在 Python 3.8 中修复。一个补丁也被反向移植到 Python 3.7.3。相关问题和拉取请求:

我有一个类似的问题,测试了一些检查被模拟的可调用签名的代码。我通过将__signature__模拟的属性设置为原始属性来解决它:

from inspect import signature
from unittest.mock import patch

def foo(a, b, c):
    pass

foo_sig = signature(foo)  # Need to cache the signature before mocking foo

with patch("__main__.foo") as mock:
    mock.__signature__ = foo_sig
    print(signature(mock))  # Prints '(a, b, c)'

这是有效的,因为函数在尝试其他方法之前signature()遵循属性,如PEP 362中所述:__signature__

如果对象有一个__signature__属性,如果没有None- 返回它

不幸的是,函数或模块的文档中signature()没有提到这一点。inspect


推荐阅读