试图理解 Spec 和 Autospec 之间的区别。他们似乎是差不多的。具体来说,如果你看看mock.patch装饰器。
有人可以解释何时使用哪个吗?
spec
仅适用于指定它的模拟实例。特别是,如果模拟类 a
有一个方法,例如method()
,然后在 a
的实例化模拟中调用该方法将自动生成并返回另一个不受任何规范限制的模拟。这就是 autospec
派上用场的地方,因为它递归地定义了所调用的规范(在迄今为止定义的规范的限制范围内)。
来自 模拟自动指定助手文档:
如果您使用类或实例作为模拟的规范,那么您只能访问真实类中存在的模拟上的属性:
>>> import urllib2
>>> mock = Mock(spec=urllib2.Request)
>>> mock.assret_called_with
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'assret_called_with'
该规范仅适用于模拟本身,因此我们对模拟上的任何方法仍然存在相同的问题:
>>> mock.has_data()
<mock.Mock object at 0x...>
>>> mock.has_data.assret_called_with()
自动指定解决了这个问题。
如果没有
spec
和 autospec
,模拟对象 不具有目标中的属性。
使用
spec=True
,模拟对象具有来自目标的属性,但这些属性的类型只是 MagicMock
没有规范。
使用
autospec=True
,模拟对象具有来自目标的属性,这些属性的类型是MagicMock
,这些模拟属性也有自己的规格。
以下代码显示了差异:
from unittest import mock
import inspect
class Bar:
def __init__(self, id: int):
self.id = id
def f(name: str):
return f'id={id}, name={name}'
class Foo:
def g(self, bar: Bar) -> Bar:
print(bar.f('world'))
return bar
def main() -> None:
print('Mock without spec')
with mock.patch(f'{Foo.__module__}.Foo') as foo:
print(f'dir(foo): {dir(foo)}') # `g` is not defined
g = getattr(foo, 'g') # `g` is defined when being accessed
print(f'foo.g signature: {inspect.signature(g)}')
print('\n')
print('Mock with spec')
with mock.patch(f'{Foo.__module__}.Foo', spec=True) as foo:
print(f'dir(foo): {dir(foo)}') # `g` is defined without a spec
g = getattr(foo, 'g')
print(f'foo.g signature: {inspect.signature(g)}')
print('\n')
print('Mock with autospec')
with mock.patch(f'{Foo.__module__}.Foo', autospec=True) as foo:
print(f'dir(foo): {dir(foo)}') # `g` is defined with a spec
g = getattr(foo, 'g')
print(f'foo.g signature: {inspect.signature(g)}')
print('\n')
if __name__ == '__main__':
main()
输出:
Mock without spec
dir(foo): ['assert_any_call', 'assert_called', 'assert_called_once', 'assert_called_once_with', 'assert_called_with', 'assert_has_calls', 'assert_not_called', 'attach_mock', 'call_args', 'call_args_list', 'call_count', 'called', 'configure_mock', 'method_calls', 'mock_add_spec', 'mock_calls', 'reset_mock', 'return_value', 'side_effect']
foo.g signature: (*args, **kwargs)
Mock with spec
dir(foo): ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'assert_any_call', 'assert_called', 'assert_called_once', 'assert_called_once_with', 'assert_called_with', 'assert_has_calls', 'assert_not_called', 'attach_mock', 'call_args', 'call_args_list', 'call_count', 'called', 'configure_mock', 'g', 'method_calls', 'mock_add_spec', 'mock_calls', 'reset_mock', 'return_value', 'side_effect']
foo.g signature: (*args, **kwargs)
Mock with autospec
dir(foo): ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'assert_any_call', 'assert_called', 'assert_called_once', 'assert_called_once_with', 'assert_called_with', 'assert_has_calls', 'assert_not_called', 'attach_mock', 'call_args', 'call_args_list', 'call_count', 'called', 'configure_mock', 'g', 'method_calls', 'mock_add_spec', 'mock_calls', 'reset_mock', 'return_value', 'side_effect']
foo.g signature: (bar: __main__.Bar) -> __main__.Bar
我将在 unittest.mock.patch 上解释它,因为它有两个选项
spec
,预计将成为对象用作模拟和 autospec
的规范 - 类似于规范,除了它会提供规范对象的所有“子”模拟的规范,下面是这些用例的说明:
from unittest.mock import Mock, create_autospec, patch
from unittest import TestCase
class MyClass:
@staticmethod
def method(foo, bar):
print(foo)
def some_method(some_class: MyClass):
arg = 1
# Would fail because of wrong parameters passed to method.
return some_class.method(arg)
class TestSomethingTestCase(TestCase):
def test_something_with_patch_spec(self):
with patch(f'{__name__}.MyClass', spec=True) as mock:
# Fails because of signature misuse.
result = some_method(mock)
self.assertTrue(result)
self.assertTrue(mock.method.called)
def test_second_with_patch_autospec(self):
with patch(f'{__name__}.MyClass', autospec=True) as mock:
# Fails because of signature misuse.
result = some_method(mock)
self.assertTrue(result)
self.assertTrue(mock.method.called)
使用 autospec 的测试用例将捕获代码中的错误,因为
MyClass.method
签名与 some_method
上的预期不符,这是预期的,而使用规范的 test_something_with_patch_spec
会给您带来 误报,所以测试无法履行其职责。
unittest.mock.Mock只有spec选项,作为替代方案,您可以使用unittest.mock.create_autospec,因此它将创建具有上面示例中描述的autospec效果的Mock。