修补类会产生"AttributeError:模拟对象没有属性".访问实例属性时 [英] patching a class yields "AttributeError: Mock object has no attribute" when accessing instance attributes
问题描述
问题
将mock.patch
与autospec=True
结合使用可修补类,这并不保留该类实例的属性.
The Problem
Using mock.patch
with autospec=True
to patch a class is not preserving attributes of instances of that class.
详细信息
我正在尝试测试类Bar
,该类将类Foo
的实例实例化为名为foo
的Bar
对象属性.被测试的Bar
方法称为bar
;它调用属于Bar
的Foo
实例的方法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
将返回该属性的自动指定的模拟.如果您的类Spam
的eggs
属性是类型为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屋!