在python 3.5中模拟异步调用
如何使用在python 3.5中模拟异步调用,python,python-asyncio,python-mock,Python,Python Asyncio,Python Mock,如何使用unittest.mock.patch模拟从一个本机协程到另一个本机协程的异步调用 我目前有一个相当尴尬的解决方案: class CoroutineMock(MagicMock): def __await__(self, *args, **kwargs): future = Future() future.set_result(self) result = yield from future return resul
unittest.mock.patch
模拟从一个本机协程到另一个本机协程的异步调用
我目前有一个相当尴尬的解决方案:
class CoroutineMock(MagicMock):
def __await__(self, *args, **kwargs):
future = Future()
future.set_result(self)
result = yield from future
return result
@patch('some.path')
def test(self, mock):
f = asyncio.Future()
f.set_result('whatever result you want')
process_smtp_message.return_value = f
mock.assert_called_with(1, 2, 3)
然后
这可以用,但看起来很难看。还有其他类似python的方法吗?模拟协同程序的另一种方法是生成返回mock的协同程序。通过这种方式,您可以模拟将被传递到
asyncio.wait
或asyncio.wait\u for
的协同程序
这使得更通用的协同程序变得更加繁琐:
def make_coroutine(mock)
async def coroutine(*args, **kwargs):
return mock(*args, **kwargs)
return coroutine
class Test(TestCase):
def setUp(self):
self.coroutine_mock = Mock()
self.patcher = patch('some.coroutine',
new=make_coroutine(self.coroutine_mock))
self.patcher.start()
def tearDown(self):
self.patcher.stop()
解决方案其实很简单: 我只需要将mock的
\uuuu call\uuu
方法转换为协同程序:
class AsyncMock(MagicMock):
async def __call__(self, *args, **kwargs):
return super(AsyncMock, self).__call__(*args, **kwargs)
这非常有效,当调用mock时,代码接收本机协同路由
用法示例:
@mock.patch('my.path.asyncio.sleep', new_callable=AsyncMock)
def test_stuff(sleep):
# code
子类化
MagicMock
将为从协同程序mock生成的所有mock传播自定义类。例如,AsyncMock()。\uuu str\uu
也将成为一个AsyncMock
,这可能不是您想要的
相反,您可能希望定义一个工厂,该工厂创建一个带有自定义参数的Mock
(或MagicMock
),例如side\u effect=coroutine(coro)
。另外,将协程函数与协程分离可能是一个好主意(如中所述)
以下是我的想法:
from asyncio import coroutine
def CoroMock():
coro = Mock(name="CoroutineResult")
corofunc = Mock(name="CoroutineFunction", side_effect=coroutine(coro))
corofunc.coro = coro
return corofunc
对不同对象的解释:
:coroutine函数mockcorofunc
:为每个调用生成的协同程序corofunc.side\u effect()
:协同程序用于获取结果的模拟corofunc.coro
:协同程序返回的值corofunc.coro.return\u值
:可能用于引发异常corofunc.coro.side\u effect
async def coro(a, b):
return await sleep(1, result=a+b)
def some_action(a, b):
return get_event_loop().run_until_complete(coro(a, b))
@patch('__main__.coro', new_callable=CoroMock)
def test(corofunc):
a, b, c = 1, 2, 3
corofunc.coro.return_value = c
result = some_action(a, b)
corofunc.assert_called_with(a, b)
assert result == c
每个人都缺少最简单、最清晰的解决方案:
class CoroutineMock(MagicMock):
def __await__(self, *args, **kwargs):
future = Future()
future.set_result(self)
result = yield from future
return result
@patch('some.path')
def test(self, mock):
f = asyncio.Future()
f.set_result('whatever result you want')
process_smtp_message.return_value = f
mock.assert_called_with(1, 2, 3)
请记住,协同程序可以被视为一个函数,它保证返回一个未来,而未来可以等待。基于@scolvin answer,我创建了这个(imo)更干净的方法:
import asyncio
def async_return(result):
f = asyncio.Future()
f.set_result(result)
return f
就是这样,只需在想要异步的任何返回周围使用它,如
mock = MagicMock(return_value=async_return("Example return"))
await mock()
模拟异步对象的“最简单”解决方案的另一个变体,它只是一个线性程序
资料来源:
class Yo:
async def foo(self):
await self.bar()
async def bar(self):
# Some code
在测试中:
from asyncio import coroutine
yo = Yo()
# Here bounded method bar is mocked and will return a customised result.
yo.bar = Mock(side_effect=coroutine(lambda:'the awaitable should return this'))
event_loop.run_until_complete(yo.foo())
您可以这样设置异步方法的
返回值
:
mock=unittest.mock.MagicMock()
mock.your\u async\u method.return\u value=task\u from\u result(your\u return\u值)
异步定义任务\u来自\u结果(结果):
返回结果
调用方必须执行
等待您的\u async\u方法(..)
,就像该方法没有被模拟一样。此外,由于asyncio.tasks,此模拟不能与asyncio.wait一起工作。请确保\u futuratris很好,但不能与autospec一起使用,autospec在使用MagicMock时基本上是强制性的。你有没有想过如何让它发挥作用?我对它的内部结构还不够熟悉……它对我来说非常适合。我是这样使用的:``@mock.patch('my.path.asyncio.sleep',new_callable=asynchmock,)def test_stuff(sleep):#code``这很有效。我最初喜欢下面Ivan Castellanos的另一个解决方案。但未来永远不会实现,我拼命尝试,但没有成功。这是最方便、最优雅的。至少用于基本用途。注意:AsyncMock
在unittest中可用。mock
从python 3.8开始,mock还自动检测应该使用它的位置(请参阅)。什么是进程\u smtp\u消息。返回\u值=f?另外,对正在测试的函数的调用在哪里?@Skorpeo-我认为他的意思是mock.return\u value=fI肯定不是。Mock是测试夹具。进程\u smtp\u消息显然是您试图模拟的内容。该解决方案与Python 3.8不兼容,Python 3.8引入了本机AsyncMock
。因此,由于将来的类问题,带有解决方案的代码将失败并出现错误。但是一个带有简单的AsyncMock
实现的Zozz解决方案可以在Python 3.7(例如)和Python 3.8中工作(如果您要有条件地导入本机AsyncMock
),这不起作用,副作用=协同路由(coro),协同路由没有定义我实际上更像原始解决方案,因为它不需要对测试函数进行特殊重写。与问题中显示的方法相比,这种方法有什么优势?