問題描述
問題
將 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()
這篇關于修補類會產生“AttributeError:Mock object has no attribute";訪問實例屬性時的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!