在一些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
?
答案 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
。这在修补实例时也有效,因为直接在实例上设置属性时不使用描述符协议。