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
patching a class yields "AttributeError: Mock object has no attribute" when accessing instance attributes
提问by Clandestine
The Problem
Using mock.patch
with autospec=True
to patch a class is not preserving attributes of instances of that class.
使用mock.patch
withautospec=True
修补类的问题不是保留该类实例的属性。
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:
详细信息
我想测试一个类Bar
实例化类的一个实例Foo
为Bar
所谓的对象属性foo
。所述Bar
被测方法被调用bar
; 它调用属于foo
的Foo
实例的方法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_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'"
类和方法工作得很好(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 foo
is 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
. 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.
注意:
这是一个显示基本问题的简单示例。实际上,我正在模拟一个第三方 module.Class -- consul.Consul
-- 我在我拥有的 Consul 包装器类中实例化了它的客户端。由于我不维护 consul 模块,因此我无法修改源代码以适合我的测试(无论如何我真的不想这样做)。对于它的价值,consul.Consul()
返回一个 consul 客户端,它具有一个属性kv
- 的实例consul.Consul.KV
。kv
有一个方法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 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.
自动指定的模拟的递归性质然后仅限于那些静态属性;如果foo
是类属性,访问Foo().foo
将返回该属性的自动指定模拟。如果您有一个类Spam
的eggs
属性是 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.autospec
can'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 Foo
class 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()