修补类会产生“AttributeError:Mock object has no attribute";访问实

时间:2022-10-18
本文介绍了修补类会产生“AttributeError:Mock object has no attribute";访问实例属性时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

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

The Problem
Using mock.patch with autospec=True to patch a class is not preserving attributes of instances of that class.

详情
我正在尝试测试一个类 Bar,它将类 Foo 的实例实例化为名为 fooBar 对象属性.被测的Bar方法叫做bar;它调用属于 BarFoo 实例的方法 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#autospeccinghttp://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";访问实例属性时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!