使用mock.patch.object将方法包装在类的所有对象上

时间:2017-05-30 16:05:45

标签: python python-3.x python-unittest

在一些asyncio测试用例中,我发现我经常想等待一些方法被调用,所以我编写了以下上下文管理器:

@contextmanager
def wait_for_call(loop, obj, method, calls = 1):
    # set up some bookkeeping including a future called fut
    def cb(*args, **kwards):
        # Set fut's result to true if the right number of calls happen
    try:
        with mock.patch.object(obj, method,
                               wraps = getattr(obj, method),
                               side_effect = cb):
            yield
            loop.run_until_complete(asyncio.wait_for(fut, 0.5))
    except  asyncio.futures.TimeoutError:
        raise AssertionError("Timeout waiting for call to {} of {}".format(
            method, obj)) from None

如果我要修补特定实例或者我正在修补类方法,那么这很有用。但是,有几种情况我想修补这样的常规(实例)方法:

class foo:
    def bar(self): pass
x = foo()
with wait_for_call(loop, x, 'bar'): x.bar()

当我这样做时,我得到TypeError,因为x.bar没有得到self。我认为这是因为MagicMock没有像函数那样实现描述符协议。 如何包装方法并正确处理self

1 个答案:

答案 0 :(得分:0)

在python3函数中实现描述符协议,因此有一个__get__方法。

  
    
      

def foo(self):传球           ...       FOO。获取                  因此,当函数作为属性附加到类,并尝试从该类的实例检索该方法时,将调用函数对象的__get__方法。这就是self与第一个参数绑定的方式。例如,通过合成对foo __get__的调用,我们可以获得foo的版本,该版本似乎是字符串的方法。

    
  
>>> a = "I am a string"
>>> foo.__get__(a,str)
<bound method foo of 'I am a string'>

MagicMock不会这样做:

>>> m = mock.MagicMock()
>>> m.foo()
<MagicMock name='mock.foo()' id='140643153651136'>
>>> m.foo.__get__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.5/unittest/mock.py", line 582, in __getattr__
    raise AttributeError(name)
AttributeError: __get__

似乎可以在MagicMock上明确设置__get__,但为了实现问题的目标,有一个更简单的方法。 patch.object方法可用于修补任何内容,而不仅仅是MagicMock。如果不需要MagicMock的任何功能,那么您只需使用新方法进行修补。

def wait_for_call(loop, obj, method, calls = 1):
    # set up a future and call tracking
    def cb(*args, **kwargs):
        # Track number of calls and set a future when done
        return wraps(*args, **kwargs)
    try:
        wraps = getattr(obj, method)
        with mock.patch.object(obj, method,
                               new= cb):
            yield
            loop.run_until_complete(asyncio.wait_for(fut, 0.5))
    except  asyncio.futures.TimeoutError:
        raise AssertionError("Timeout waiting for call to {} of {}".format(
            method, obj)) from None

cb已修补到对象中,因为cb是一个函数,它正确获取self。这在修补实例时也有效,因为直接在实例上设置属性时不使用描述符协议。