在某些异步测试用例中,我发现我经常想等待某种方法被调用,所以我已经写了以下上下文管理器:
@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
。当实例修补一个实例时,这也起作用,因为当直接在实例上设置属性时未使用描述符协议。