如何使用 python mock 直接模拟超类? [英] How do I directly mock a superclass with python mock?

查看:36
本文介绍了如何使用 python mock 直接模拟超类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 python 模拟框架进行测试 (http://www.voidspace.org.uk/python/mock/),我想模拟一个超类并专注于测试子类的添加行为.

I am using the python mock framework for testing (http://www.voidspace.org.uk/python/mock/) and I want to mock out a superclass and focus on testing the subclasses' added behavior.

(对于那些感兴趣的人,我已经扩展了 pymongo.collection.Collection,我只想测试我添加的行为.我不想为了测试目的而将 mongodb 作为另一个进程运行.)

(For those interested I have extended pymongo.collection.Collection and I want to only test my added behavior. I do not want to have to run mongodb as another process for testing purposes.)

对于本次讨论,A 是超类,B 是子类.此外,我定义了直接和间接超类调用,如下所示:

For this discussion, A is the superclass and B is the subclass. Furthermore, I define direct and indirect superclass calls as shown below:

class A(object):
    def method(self):
        ...

    def another_method(self):
        ...

class B(A):
    def direct_superclass_call(self):
        ...
        A.method(self)

    def indirect_superclass_call(self):
        ...
        super(A, self).another_method()

方法#1

A 定义一个名为 MockA 的模拟类,并使用 mock.patch 将其替换为运行时的测试.这处理直接超类调用.然后操纵 B.__bases__ 来处理间接超类调用.(见下文)

Approach #1

Define a mock class for A called MockA and use mock.patch to substitute it for the test at runtime. This handles direct superclass calls. Then manipulate B.__bases__ to handle indirect superclass calls. (see below)

出现的问题是我必须编写 MockA 并且在某些情况下(如 pymongo.collection.Collection 的情况)这可能涉及很多努力解开所有模拟出来的内部调用.

The issue that arises is that I have to write MockA and in some cases (as in the case for pymongo.collection.Collection) this can involve a lot of work to unravel all of the internal calls to mock out.

所需的方法是以某种方式使用 mock.Mock() 类来及时处理对 mock 的调用,以及在测试中定义的 return_value 或 side_effect.这样一来,我就可以通过避免 MockA 的定义来做更少的工作.

The desired approach is to somehow use a mock.Mock() class to handle calls on the the mock just in time, as well as defined return_value or side_effect in place in the test. In this manner, I have to do less work by avoiding the definition of MockA.

我遇到的问题是我无法弄清楚如何更改 B.__bases__ 以便可以将 mock.Mock() 的实例作为超类放置(我必须需要以某种方式在这里进行一些直接绑定).到目前为止,我已经确定,super() 检查 MRO,然后调用定义相关方法的第一个类.我无法弄清楚如何让超类处理对它的检查并在遇到模拟类时成功.在这种情况下,似乎没有使用 __getattr__ .我希望超级认为该方法已在此时定义,然后像往常一样使用 mock.Mock() 功能.

The issue that I am having is that I cannot figure out how to alter B.__bases__ so that an instance of mock.Mock() can be put in place as a superclass (I must need to somehow do some direct binding here). Thus far I have determined, that super() examines the MRO and then calls the first class that defines the method in question. I cannot figure out how to get a superclass to handle the check to it and succeed if it comes across a mock class. __getattr__ does not seem to be used in this case. I want super to to think that the method is defined at this point and then use the mock.Mock() functionality as usual.

super() 如何发现在 MRO 序列中的类中定义了哪些属性?有没有办法让我在这里插话并以某种方式让它动态利用 mock.Mock()?

How does super() discover what attributes are defined within the class in the MRO sequence? And is there a way for me to interject here and to somehow get it to utilize a mock.Mock() on the fly?

import mock

class A(object):
    def __init__(self, value):
        self.value = value      

    def get_value_direct(self):
        return self.value

    def get_value_indirect(self):
        return self.value   

class B(A):
    def __init__(self, value):
        A.__init__(self, value)

    def get_value_direct(self):
        return A.get_value_direct(self)

    def get_value_indirect(self):
        return super(B, self).get_value_indirect()


# approach 1 - use a defined MockA
class MockA(object):
    def __init__(self, value):
        pass

    def get_value_direct(self):
        return 0

    def get_value_indirect(self):
        return 0

B.__bases__ = (MockA, )  # - mock superclass 
with mock.patch('__main__.A', MockA):  
    b2 = B(7)
    print '
Approach 1'
    print 'expected result = 0'
    print 'direct =', b2.get_value_direct()
    print 'indirect =', b2.get_value_indirect()
B.__bases__ = (A, )  # - original superclass 


# approach 2 - use mock module to mock out superclass

# what does XXX need to be below to use mock.Mock()?
#B.__bases__ = (XXX, )
with mock.patch('__main__.A') as mymock:  
    b3 = B(7)
    mymock.get_value_direct.return_value = 0
    mymock.get_value_indirect.return_value = 0
    print '
Approach 2'
    print 'expected result = 0'
    print 'direct =', b3.get_value_direct()
    print 'indirect =', b3.get_value_indirect() # FAILS HERE as the old superclass is called
#B.__bases__ = (A, )  # - original superclass

推荐答案

我按照 kindall 的建议模拟了 super().不幸的是,经过大量的努力,处理复杂的继承情况变得相当复杂.

I played around with mocking out super() as suggested by kindall. Unfortunately, after a great deal of effort it became quite complicated to handle complex inheritance cases.

经过一些工作,我意识到 super() 在通过 MRO 解析属性时直接访问类的 __dict__ (它不执行 getattr 类型的调用).解决方案是扩展一个 mock.MagicMock() 对象并用一个类包装它来完成此操作.然后可以将包装的类放在子类的 __bases__ 变量中.

After some work I realized that super() accesses the __dict__ of classes directly when resolving attributes through the MRO (it does not do a getattr type of call). The solution is to extend a mock.MagicMock() object and wrap it with a class to accomplish this. The wrapped class can then be placed in the __bases__ variable of a subclass.

被包装的对象将目标类的所有已定义属性反映到包装类的 __dict__ 中,以便 super() 调用解析为内部 MagicMock() 中正确修补的属性.

The wrapped object reflects all defined attributes of the target class to the __dict__ of the wrapping class so that super() calls resolve to the properly patched in attributes within the internal MagicMock().

以下代码是迄今为止我发现的可行的解决方案.请注意,我实际上是在上下文处理程序中实现的.此外,如果从其他模块导入,则必须注意修补适当的命名空间.

The following code is the solution that I have found to work thus far. Note that I actually implement this within a context handler. Also, care has to be taken to patch in the proper namespaces if importing from other modules.

这是一个说明该方法的简单示例:

This is a simple example illustrating the approach:

from mock import MagicMock
import inspect 


class _WrappedMagicMock(MagicMock):
    def __init__(self, *args, **kwds):
        object.__setattr__(self, '_mockclass_wrapper', None)
        super(_WrappedMagicMock, self).__init__(*args, **kwds)

    def wrap(self, cls):
        # get defined attribtues of spec class that need to be preset
        base_attrs = dir(type('Dummy', (object,), {}))
        attrs = inspect.getmembers(self._spec_class)
        new_attrs = [a[0] for a in attrs if a[0] not in base_attrs]

        # pre set mocks for attributes in the target mock class
        for name in new_attrs:
            setattr(cls, name, getattr(self, name))

        # eat up any attempts to initialize the target mock class
        setattr(cls, '__init__', lambda *args, **kwds: None)

        object.__setattr__(self, '_mockclass_wrapper', cls)

    def unwrap(self):
        object.__setattr__(self, '_mockclass_wrapper', None)

    def __setattr__(self, name, value):
        super(_WrappedMagicMock, self).__setattr__(name, value)

        # be sure to reflect to changes wrapper class if activated
        if self._mockclass_wrapper is not None:
            setattr(self._mockclass_wrapper, name, value)

    def _get_child_mock(self, **kwds):
        # when created children mocks need only be MagicMocks
        return MagicMock(**kwds)


class A(object):
    x = 1

    def __init__(self, value):
        self.value = value

    def get_value_direct(self):
        return self.value

    def get_value_indirect(self):
        return self.value


class B(A):
    def __init__(self, value):
        super(B, self).__init__(value)

    def f(self):
        return 2

    def get_value_direct(self):
        return A.get_value_direct(self)

    def get_value_indirect(self):
        return super(B, self).get_value_indirect()

# nominal behavior
b = B(3)
assert b.get_value_direct() == 3
assert b.get_value_indirect() == 3
assert b.f() == 2
assert b.x == 1

# using mock class
MockClass = type('MockClassWrapper', (), {})
mock = _WrappedMagicMock(A)
mock.wrap(MockClass)

# patch the mock in
B.__bases__ = (MockClass, )
A = MockClass

# set values within the mock
mock.x = 0
mock.get_value_direct.return_value = 0
mock.get_value_indirect.return_value = 0

# mocked behavior
b = B(7)
assert b.get_value_direct() == 0
assert b.get_value_indirect() == 0
assert b.f() == 2
assert b.x == 0

这篇关于如何使用 python mock 直接模拟超类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆