访问实例属性时,修补类会产生“AttributeError:Mock object has no attribute”

Posted

技术标签:

【中文标题】访问实例属性时,修补类会产生“AttributeError:Mock object has no attribute”【英文标题】:patching a class yields "AttributeError: Mock object has no attribute" when accessing instance attributes 【发布时间】:2015-10-20 23:39:07 【问题描述】:

问题 使用 mock.patchautospec=True 修补类不会保留该类实例的属性。

详情 我正在尝试测试一个类Bar,它将类Foo 的实例实例化为一个名为Bar 的对象属性foo。被测的Bar方法称为bar;它调用属于BarFoo 实例的方法foo。在测试这个时,我在嘲笑Foo,因为我只想测试Bar 正在访问正确的Foo 成员:

import unittest
from mock import patch

class Foo(object):
    def __init__(self):
        self.foo = 'foo'

class Bar(object):
    def __init__(self):
        self.foo = Foo()

    def bar(self):
        return self.foo.foo

class TestBar(unittest.TestCase):
    @patch('foo.Foo', autospec=True)
    def test_patched(self, mock_Foo):
        Bar().bar()

    def test_unpatched(self):
        assert Bar().bar() == 'foo'

类和方法工作得很好(test_unpatched 通过),但是当我尝试使用autospec=True 在测试用例中 Foo(使用nosetests 和pytest 测试)时,我遇到“AttributeError: Mock object has no attribute '富'"

19:39 $ nosetests -sv foo.py
test_patched (foo.TestBar) ... ERROR
test_unpatched (foo.TestBar) ... ok

======================================================================
ERROR: test_patched (foo.TestBar)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/mock.py", line 1201, in patched
    return func(*args, **keywargs)
  File "/home/vagrant/dev/constellation/test/foo.py", line 19, in test_patched
    Bar().bar()
  File "/home/vagrant/dev/constellation/test/foo.py", line 14, in bar
    return self.foo.foo
  File "/usr/local/lib/python2.7/dist-packages/mock.py", line 658, in __getattr__
    raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'foo'

确实,当我打印出mock_Foo.return_value.__dict__ 时,我可以看到foo 不在子项或方法列表中:

'_mock_call_args': None,
 '_mock_call_args_list': [],
 '_mock_call_count': 0,
 '_mock_called': False,
 '_mock_children': ,
 '_mock_delegate': None,
 '_mock_methods': ['__class__',
                   '__delattr__',
                   '__dict__',
                   '__doc__',
                   '__format__',
                   '__getattribute__',
                   '__hash__',
                   '__init__',
                   '__module__',
                   '__new__',
                   '__reduce__',
                   '__reduce_ex__',
                   '__repr__',
                   '__setattr__',
                   '__sizeof__',
                   '__str__',
                   '__subclasshook__',
                   '__weakref__'],
 '_mock_mock_calls': [],
 '_mock_name': '()',
 '_mock_new_name': '()',
 '_mock_new_parent': <MagicMock name='Foo' spec='Foo' id='38485392'>,
 '_mock_parent': <MagicMock name='Foo' spec='Foo' id='38485392'>,
 '_mock_wraps': None,
 '_spec_class': <class 'foo.Foo'>,
 '_spec_set': None,
 'method_calls': []

我对 autospec 的理解是,如果为 True,补丁规范应该递归应用。既然 foo 确实是 Foo 实例的一个属性,不应该打补丁吗?如果没有,我如何让 Foo 模拟来保留 Foo 实例的属性?

注意: 这是一个显示基本问题的简单示例。实际上,我在模拟第三方模块。类——consul.Consul——我在我拥有的 Consul 包装类中实例化了它的客户端。由于我不维护 consul 模块,因此我无法修改源代码以适应我的测试(无论如何我都不想这样做)。对于它的价值,consul.Consul() 返回一个领事客户端,它有一个属性kv——consul.Consul.KV 的一个实例。 kv 有一个方法 get,我将它包装在我的 Consul 类中的实例方法 get_key 中。修补consul.Consul后,get调用失败,原因是AttributeError: Mock object has no attribute kv。

已检查资源:

http://mock.readthedocs.org/en/latest/helpers.html#autospeccing http://mock.readthedocs.org/en/latest/patch.html

【问题讨论】:

这将需要模拟来创建类的实例。这绝不是一个好主意,因为这将要求它首先执行您试图用模拟替换的代码。 不相关的旁注,但这个问题以一种非常漂亮的方式提出。我喜欢 OP 如何描述他们的情况、他们的期望、展示和示例,然后解释他们的真实世界情况,然后是他们看过的参考资料。荣誉 【参考方案1】:

不,autospeccing 不能模拟在原始类的 __init__ 方法(或任何其他方法)中设置的属性。它只能模拟出静态属性,所有可以在类中找到的东西。

否则,模拟必须首先创建您尝试用模拟替换的类的实例,这不是一个好主意(想想在实例化时创建大量实际资源的类)。

自动指定的 mock 的递归性质仅限于那些静态属性;如果foo 是类属性,则访问Foo().foo 将返回该属性的自动指定模拟。如果你有一个类Spam,其eggs 属性是Ham 类型的对象,那么Spam.eggs 的模拟将是Ham 类的自动指定模拟。

documentation you read明确涵盖了这一点:

一个更严重的问题是,实例属性通常在__init__ 方法中创建,而在类中根本不存在。 autospec 无法知道任何动态创建的属性,并将 api 限制为可见属性。

您应该自己设置缺少的属性:

@patch('foo.Foo', autospec=Foo)
def test_patched(self, mock_Foo):
    mock_Foo.return_value.foo = 'foo'
    Bar().bar()

或创建Foo 类的子类,用于将属性添加为类属性的测试目的:

class TestFoo(foo.Foo):
    foo = 'foo'  # class attribute

@patch('foo.Foo', autospec=TestFoo)
def test_patched(self, mock_Foo):
    Bar().bar()

【讨论】:

是的,不知道我是怎么错过的。这是非常明确的。我想这就是我必须要做的。我希望避免这种情况,以便测试验证 Consul 类的正确使用。我想我将不得不依赖更严格的集成测试。【参考方案2】:

patch 中只有一个 create kwarg,当设置为 True 时,如果该属性尚不存在,则会创建该属性。

如果传入create=True,并且该属性不存在,则patch 将在调用修补函数时为您创建属性, 并在补丁功能退出后再次删除。

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

【讨论】:

以上是关于访问实例属性时,修补类会产生“AttributeError:Mock object has no attribute”的主要内容,如果未能解决你的问题,请参考以下文章

为啥将 std::mutex 引入成员类会产生此编译错误?

在同一包中子类化 sun.* 类会产生 IllegalAccessError

`__getattribute__` 和 `__getattr__` 中的错误处理

ES6 Class(类)

VUE-实例对象

直接访问与在开发工具中读取对象时访问 javascript 属性会产生不同的结果