修补类会产生“AttributeError:Mock object has no attribute";访问实例属性时
问题描述
问题
将 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
,我遇到AttributeError: Mock object has no attribute '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?
注意:
这是一个显示基本问题的简单示例.实际上,我正在模拟第三方 module.Class -- consul.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.
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
解决方案
不,autospeccing 不能模拟在原始类的 __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.
您阅读的文档 明确em> 涵盖了这个:
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=Foo)
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()
相关文章