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



在某些异步测试用例中,我发现我经常想等待某种方法被调用,所以我已经写了以下上下文管理器:

@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

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

def foo(self(:通过 ... foo。 get 因此,当将函数附加到类中作为属性时,您尝试从该类的实例中检索该方法时,函数对象的__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。当实例修补一个实例时,这也起作用,因为当直接在实例上设置属性时未使用描述符协议。

最新更新