修补类在访问实例属性时会产生"AttributeError:Mock对象没有属性"

Cla*_*ine 34 python attributes unit-testing mocking python-unittest

问题
使用mock.patchautospec=True修补一类不保存该类的实例的属性.

细节
我试图测试一个类Bar,它将类的实例实例Foo化为一个Bar被调用的对象属性foo.所述Bar被测方法被调用bar; 它调用属于fooFoo实例的方法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'
Run Code Online (Sandbox Code Playgroud)

类和方法工作得很好(test_unpatched通过),但是当我尝试在测试用例(使用nosetests和pytest测试)中使用Foo时autospec=True,遇到"AttributeError:Mock对象没有属性'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'
Run Code Online (Sandbox Code Playgroud)

实际上,当我打印出来时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': []}
Run Code Online (Sandbox Code Playgroud)

我对autospec的理解是,如果为True,补丁规范应该递归应用.由于foo确实是Foo实例的一个属性,它是不是应该修补?如果没有,我如何让Foo mock保留Foo实例的属性?

注意:
这是一个显示基本问题的简单示例.实际上,我正在嘲笑第三方模块.类 - consul.Consul- 我的客户端我在Consul包装器类中实例化.由于我没有维护领事模块,我无法修改源代码以适合我的测试(我不会真的想要这样做).为了它的价值,consul.Consul()返回一个consul客户端,它有一个属性kv- 一个实例consul.Consul.KV.kv有一个方法get,我get_key在Consul类的实例方法中包装.修补后consul.Consul,由于AttributeError,调用get失败:Mock对象没有属性kv.

资源已经检查:

http://mock.readthedocs.org/en/latest/helpers.html#autospeccing http://mock.readthedocs.org/en/latest/patch.html

Mar*_*ers 39

不,自动指定不能模拟__init__原始类(或任何其他方法)的方法中设置的属性.它只能模拟静态属性,可以在类中找到的所有内容.

否则,mock必须首先创建一个你试图用mock替换的类的实例,这不是一个好主意(想想在实例化时创建大量实际资源的类).

然后,自动调用的模拟的递归性质仅限于那些静态属性; 如果foo是类属性,则访问Foo().foo将返回该属性的自动编译模拟.如果你有一个类Spam,其eggs属性类型的对象Ham,那么模拟Spam.eggs将是一个自动specced模拟Ham类.

您阅读文档明确涵盖了这一点:

更严重的问题是,例如在__init__方法中创建属性并且根本不存在于类中是常见的.autospec无法知道任何动态创建的属性,并将api限制为可见属性.

您应该自己设置缺少的属性:

@patch('foo.Foo', autospec=TestFoo)
def test_patched(self, mock_Foo):
    mock_Foo.return_value.foo = 'foo'
    Bar().bar()
Run Code Online (Sandbox Code Playgroud)

或者Foo为了测试目的创建类的子类,将属性添加为类属性:

class TestFoo(foo.Foo):
    foo = 'foo'  # class attribute

@patch('foo.Foo', autospec=TestFoo)
def test_patched(self, mock_Foo):
    Bar().bar()
Run Code Online (Sandbox Code Playgroud)