Python 模拟 Autospec 与 Spec

问题描述 投票:0回答:4

试图理解 Spec 和 Autospec 之间的区别。他们似乎是差不多的。具体来说,如果你看看mock.patch装饰器。

有人可以解释何时使用哪个吗?

https://docs.python.org/3/library/unittest.mock.html

python-2.7 mocking python-unittest
4个回答
6
投票

spec
用作 Mock 对象的模板。用文档的话来说:

如果您使用spec或spec_set参数,那么只会创建规范中存在的魔术方法。

这意味着您无法在模拟对象上调用您正在模拟的对象上不存在的方法。 文档这样解释:

注意如果您使用spec关键字参数来创建模拟,那么尝试设置不在规范中的魔术方法将引发AttributeError。

autospec
基本上是
patch
中的简写,用于将修补的对象传递到正在创建的
spec
MagicMock
。文档:

如果设置 autospec=True,则将使用被替换对象的规范创建模拟。


6
投票

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()

自动指定解决了这个问题。


4
投票

如果没有

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

1
投票

我将在 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。

© www.soinside.com 2019 - 2024. All rights reserved.