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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31709792/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 10:27:26  来源:igfitidea点击:

patching a class yields "AttributeError: Mock object has no attribute" when accessing instance attributes

pythonunit-testingattributesmockingpython-unittest

提问by Clandestine

The Problem
Using mock.patchwith autospec=Trueto patch a class is not preserving attributes of instances of that class.


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

The Details
I am trying to test a class Barthat instantiates an instance of class Fooas a Barobject attribute called foo. The Barmethod under test is called bar; it calls method fooof the Fooinstance belonging to Bar. In testing this, I am mocking Foo, as I only want to test that Baris accessing the correct Foomember:

详细信息
我想测试一个类Bar实例化类的一个实例FooBar所谓的对象属性foo。所述Bar被测方法被调用bar; 它调用属于fooFoo实例的方法Bar。在测试这一点时,我在嘲笑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'

The classes and methods work just fine (test_unpatchedpasses), but when I try to Foo in a test case (tested using both nosetests and pytest) using autospec=True, I encounter "AttributeError: Mock object has no attribute 'foo'"

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

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'

Indeed, when I print out mock_Foo.return_value.__dict__, I can see that foois not in the list of children or methods:

确实,当我打印出来时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': []}

My understanding of autospec is that, if True, the patch specs should apply recursively. Since foo is indeed an attribute of Foo instances, should it not be patched? If not, how do I get the Foo mock to preserve the attributes of Foo instances?

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

NOTE:
This is a trivial example that shows the basic problem. In reality, I am mocking a third party module.Class -- consul.Consul-- whose client I instantiate in a Consul wrapper class that I have. As I don't maintain the consul module, I can't modify the source to suit my tests (I wouldn't really want to do that anyway). For what it's worth, consul.Consul()returns a consul client, which has an attribute kv-- an instance of consul.Consul.KV. kvhas a method get, which I am wrapping in an instance method get_keyin my Consul class. After patching consul.Consul, the call to get fails because of AttributeError: Mock object has no attribute kv.

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

Resources Already Checked:

已检查资源:

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

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

采纳答案by Martijn Pieters

No, autospeccing cannot mock out attributes set in the __init__method of the original class (or in any other method). It can only mock out static attributes, everything that can be found on the class.

不,自动指定不能模拟__init__在原始类的方法(或任何其他方法)中设置的属性。它只能模拟静态属性,可以在类上找到的所有内容。

Otherwise, the mock would have to create an instance of the class you tried to replace with a mock in the first place, which is not a good idea (think classes that create a lot of real resources when instantiated).

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

The recursive nature of an auto-specced mock is then limited to those static attributes; if foois a class attribute, accessing Foo().foowill return an auto-specced mock for that attribute. If you have a class Spamwhose eggsattribute is an object of type Ham, then the mock of Spam.eggswill be an auto-specced mock of the Hamclass.

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

The documentation you readexplicitlycovers this:

您阅读文档明确涵盖了这一点:

A more serious problem is that it is common for instance attributes to be created in the __init__method and not to exist on the class at all. autospeccan't know about any dynamically created attributes and restricts the api to visible attributes.

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

You should just setthe missing attributes yourself:

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

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

or create a subclass of your Fooclass for testing purposes that adds the attribute as a class attribute:

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

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

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