patching a class yields "AttributeError: Mock object has no attribute" when accessing instance attributes
patching a class yields "AttributeError: Mock object has no attribute" when accessing instance attributes
问题
使用 mock.patch
和 autospec=True
来修补 class 不会保留 class.
实例的属性
详情
我正在尝试测试将 class Foo
的实例实例化为名为 foo
的 Bar
对象属性的 class Bar
。被测Bar
方法调用bar
;它调用属于 Bar
的 Foo
实例的方法 foo
。在测试这个时,我在嘲笑 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'
classes 和方法工作得很好(test_unpatched
通过),但是当我尝试在测试用例中使用 Foo(同时使用 nosetests 和 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'
确实,当我打印出 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': []}
我对 autospec 的理解是,如果为真,补丁规范应该递归应用。既然 foo 确实是 Foo 实例的一个属性,难道不应该打补丁吗?如果没有,我如何获得 Foo 模拟以保留 Foo 实例的属性?
注意:
这是一个显示基本问题的简单示例。实际上,我在嘲笑第三方 module.Class -- consul.Consul
-- 我在我拥有的 Consul 包装器 class 中实例化了它的客户端。由于我不维护 consul 模块,因此我无法修改源代码以适合我的测试(无论如何我真的不想这样做)。对于它的价值,consul.Consul()
returns 一个 consul 客户端,它有一个属性 kv
-- consul.Consul.KV
的一个实例。 kv
有一个方法 get
,我将其包装在我的 Consul class 中的一个实例方法 get_key
中。打补丁后consul.Consul
,调用get失败,原因是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 无法模拟原始 class 的 __init__
方法(或任何其他方法)中设置的属性。它只能mock出静态属性,在class.
上能找到的一切
否则,mock 将不得不创建一个 class 的实例,您首先尝试用 mock 替换它,这不是一个好主意(想想 classes 创建实例化时有很多实际资源)。
auto-specced mock 的递归性质随后仅限于那些静态属性;如果 foo
是一个 class 属性,访问 Foo().foo
将 return 该属性的自动指定模拟。如果你有一个 class Spam
其 eggs
属性是类型 Ham
的对象,那么 Spam.eggs
的模拟将是自动指定的模拟Ham
class.
documentation you read明确涵盖了这个:
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.
您应该自己设置缺少的属性:
@patch('foo.Foo', autospec=Foo)
def test_patched(self, mock_Foo):
mock_Foo.return_value.foo = 'foo'
Bar().bar()
或创建 Foo
class 的子class 用于测试目的,将属性添加为 class 属性:
class TestFoo(foo.Foo):
foo = 'foo' # class attribute
@patch('foo.Foo', autospec=TestFoo)
def test_patched(self, mock_Foo):
Bar().bar()
patch 中只有一个 create kwarg,当设置为 True 时,如果该属性尚不存在,将创建该属性。
If you pass in create=True, and the attribute doesn’t exist, patch
will create the attribute for you when the patched function is called,
and delete it again after the patched function has exited.
问题
使用 mock.patch
和 autospec=True
来修补 class 不会保留 class.
详情
我正在尝试测试将 class Foo
的实例实例化为名为 foo
的 Bar
对象属性的 class Bar
。被测Bar
方法调用bar
;它调用属于 Bar
的 Foo
实例的方法 foo
。在测试这个时,我在嘲笑 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'
classes 和方法工作得很好(test_unpatched
通过),但是当我尝试在测试用例中使用 Foo(同时使用 nosetests 和 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'
确实,当我打印出 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': []}
我对 autospec 的理解是,如果为真,补丁规范应该递归应用。既然 foo 确实是 Foo 实例的一个属性,难道不应该打补丁吗?如果没有,我如何获得 Foo 模拟以保留 Foo 实例的属性?
注意:
这是一个显示基本问题的简单示例。实际上,我在嘲笑第三方 module.Class -- consul.Consul
-- 我在我拥有的 Consul 包装器 class 中实例化了它的客户端。由于我不维护 consul 模块,因此我无法修改源代码以适合我的测试(无论如何我真的不想这样做)。对于它的价值,consul.Consul()
returns 一个 consul 客户端,它有一个属性 kv
-- consul.Consul.KV
的一个实例。 kv
有一个方法 get
,我将其包装在我的 Consul class 中的一个实例方法 get_key
中。打补丁后consul.Consul
,调用get失败,原因是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 无法模拟原始 class 的 __init__
方法(或任何其他方法)中设置的属性。它只能mock出静态属性,在class.
否则,mock 将不得不创建一个 class 的实例,您首先尝试用 mock 替换它,这不是一个好主意(想想 classes 创建实例化时有很多实际资源)。
auto-specced mock 的递归性质随后仅限于那些静态属性;如果 foo
是一个 class 属性,访问 Foo().foo
将 return 该属性的自动指定模拟。如果你有一个 class Spam
其 eggs
属性是类型 Ham
的对象,那么 Spam.eggs
的模拟将是自动指定的模拟Ham
class.
documentation you read明确涵盖了这个:
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.
您应该自己设置缺少的属性:
@patch('foo.Foo', autospec=Foo)
def test_patched(self, mock_Foo):
mock_Foo.return_value.foo = 'foo'
Bar().bar()
或创建 Foo
class 的子class 用于测试目的,将属性添加为 class 属性:
class TestFoo(foo.Foo):
foo = 'foo' # class attribute
@patch('foo.Foo', autospec=TestFoo)
def test_patched(self, mock_Foo):
Bar().bar()
patch 中只有一个 create kwarg,当设置为 True 时,如果该属性尚不存在,将创建该属性。
If you pass in create=True, and the attribute doesn’t exist, patch will create the attribute for you when the patched function is called, and delete it again after the patched function has exited.