修补类会产生"AttributeError:模拟对象没有属性".访问实例属性时 [英] patching a class yields "AttributeError: Mock object has no attribute" when accessing instance attributes

查看:71
本文介绍了修补类会产生"AttributeError:模拟对象没有属性".访问实例属性时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题
mock.patchautospec=True结合使用可修补类,这并不保留该类实例的属性.

The Problem
Using mock.patch with autospec=True to patch a class is not preserving attributes of instances of that class.

详细信息
我正在尝试测试类Bar,该类将类Foo的实例实例化为名为fooBar对象属性.被测试的Bar方法称为bar;它调用属于BarFoo实例的方法foo.在测试中,我嘲笑了Foo,因为我只想测试Bar正在访问正确的Foo成员:

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

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测试时(使用鼻子测试和pytest进行测试),我遇到"AttributeError:Mock对象没有属性'foo'"

The classes and methods work just fine (test_unpatched passes), 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'"

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不在子级或方法列表中:

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

{'_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实例的属性?

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?

注意:
这是一个简单的例子,显示了基本问题.实际上,我在嘲笑第三方模块.类-consul.Consul-我在我拥有的Consul包装器类中实例化了其客户端.由于我不维护consul模块,因此无法修改源代码以使其适合我的测试(无论如何,我实际上都不想这样做).就其价值而言,consul.Consul()返回一个领事客户,该客户具有属性kv-consul.Consul.KV的实例. kv有一个方法get,我将其包装在Consul类的实例方法get_key中.修补consul.Consul之后,由于AttributeError:Mock对象没有属性kv,对get的调用失败.

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. kv has a method get, which I am wrapping in an instance method get_key in my Consul class. After patching consul.Consul, the call to get fails because of AttributeError: Mock object has no attribute kv.

资源已检查:

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

推荐答案

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

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.

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

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

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

The recursive nature of an auto-specced mock is then limited to those static attributes; if foo is a class attribute, accessing Foo().foo will return an auto-specced mock for that attribute. If you have a class Spam whose eggs attribute is an object of type Ham, then the mock of Spam.eggs will be an auto-specced mock of the Ham class.

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

The documentation you read explicitly covers this:

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

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. autospec can’t know about any dynamically created attributes and restricts the api to visible attributes.

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

You should just set the missing attributes yourself:

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

或创建Foo类的子类以进行测试,以将该属性添加为类属性:

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

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

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

这篇关于修补类会产生"AttributeError:模拟对象没有属性".访问实例属性时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆