如何testingPython 3.4 asyncio代码?
使用Python 3.4 asyncio
库为代码编写unit testing的最佳方式是什么? 假设我想testing一个TCP客户端( SocketConnection
):
import asyncio import unittest class TestSocketConnection(unittest.TestCase): def setUp(self): self.mock_server = MockServer("localhost", 1337) self.socket_connection = SocketConnection("localhost", 1337) @asyncio.coroutine def test_sends_handshake_after_connect(self): yield from self.socket_connection.connect() self.assertTrue(self.mock_server.received_handshake())
当使用默认testing运行器运行此testing用例时,testing将始终成功,因为该方法只执行直到第一次yield from
指令产生,然后在执行任何断言之前返回。 这导致testing总是成功。
是否有预构build的testing运行器能够处理这样的asynchronous代码?
async_test
,由Marvin Killingbuild议,绝对可以帮助 – 以及直接调用loop.run_until_complete()
但我也强烈build议为每个testing重新创build一个新的事件循环,并将循环直接传递给API调用(至lessasyncio
本身只接受每个需要它的调用的loop
关键字参数)。
喜欢
class Test(unittest.TestCase): def setUp(self): self.loop = asyncio.new_event_loop() asyncio.set_event_loop(None) def test_xxx(self): @asyncio.coroutine def go(): reader, writer = yield from asyncio.open_connection( '127.0.0.1', 8888, loop=self.loop) yield from asyncio.sleep(0.01, loop=self.loop) self.loop.run_until_complete(go())
它隔离testing用例中的testing,并防止奇怪的错误,如test_a
创build的test_a
协程,但仅在test_b
执行时间结束。
我暂时用一个由Tornado的gen_test启发的装饰器解决了这个问题:
def async_test(f): def wrapper(*args, **kwargs): coro = asyncio.coroutine(f) future = coro(*args, **kwargs) loop = asyncio.get_event_loop() loop.run_until_complete(future) return wrapper
就像JF Sebastianbuild议的那样,这个装饰器将会阻塞,直到testing方法协程完成。 这使我可以写这样的testing用例:
class TestSocketConnection(unittest.TestCase): def setUp(self): self.mock_server = MockServer("localhost", 1337) self.socket_connection = SocketConnection("localhost", 1337) @async_test def test_sends_handshake_after_connect(self): yield from self.socket_connection.connect() self.assertTrue(self.mock_server.received_handshake())
这个解决scheme可能会错过一些边缘情况。
我认为像这样的设施应该添加到Python的标准库,以使asyncio
和unittest
交互更加方便。
pytest-asyncio看起来很有希望:
@pytest.mark.asyncio async def test_some_asyncio_code(): res = await library.do_something() assert b'expected result' == res
使用这个类而不是unittest.TestCase
基类:
import asyncio import unittest class AioTestCase(unittest.TestCase): # noinspection PyPep8Naming def __init__(self, methodName='runTest', loop=None): self.loop = loop or asyncio.get_event_loop() self._function_cache = {} super(AioTestCase, self).__init__(methodName=methodName) def coroutine_function_decorator(self, func): def wrapper(*args, **kw): return self.loop.run_until_complete(func(*args, **kw)) return wrapper def __getattribute__(self, item): attr = object.__getattribute__(self, item) if asyncio.iscoroutinefunction(attr): if item not in self._function_cache: self._function_cache[item] = self.coroutine_function_decorator(attr) return self._function_cache[item] return attr class TestMyCase(AioTestCase): async def test_dispatch(self): self.assertEqual(1, 1)
你也可以使用aiounittest
Svetlov类似的方法,@Marvin杀死答案并将其包装在易于使用的AsyncTestCase
类中:
import asyncio import aiounittest async def add(x, y): await asyncio.sleep(0.1) return x + y class MyTest(aiounittest.AsyncTestCase): async def test_async_add(self): ret = await add(5, 6) self.assertEqual(ret, 11) # or 3.4 way @asyncio.coroutine def test_sleep(self): ret = yield from add(5, 6) self.assertEqual(ret, 11) # some regular test code def test_something(self): self.assertTrue(true)
正如你所看到的asynchronous情况是由AsyncTestCase
处理的。 它也支持同步testing。 有可能提供自定义的事件循环,只是覆盖AsyncTestCase.get_event_loop
。
如果你喜欢(出于某种原因)另一个TestCase类(例如unittest.TestCase
),你可以使用async_test
装饰器:
import asyncio import unittest from aiounittest import async_test async def add(x, y): await asyncio.sleep(0.1) return x + y class MyTest(unittest.TestCase): @async_test async def test_async_add(self): ret = await add(5, 6) self.assertEqual(ret, 11)
真的很喜欢https://stackoverflow.com/a/23036785/350195中提到的;async_test
包装器,这里是Python 3.5+
def async_test(coro): def wrapper(*args, **kwargs): loop = asyncio.new_event_loop() return loop.run_until_complete(coro(*args, **kwargs)) return wrapper class TestSocketConnection(unittest.TestCase): def setUp(self): self.mock_server = MockServer("localhost", 1337) self.socket_connection = SocketConnection("localhost", 1337) @async_test async def test_sends_handshake_after_connect(self): await self.socket_connection.connect() self.assertTrue(self.mock_server.received_handshake())
我通常将我的asynchronoustesting定义为协程,并使用装饰器来“同步”它们:
import asyncio import unittest def sync(coro): def wrapper(*args, **kwargs): loop = asyncio.get_event_loop() loop.run_until_complete(coro(*args, **kwargs)) return wrapper class TestSocketConnection(unittest.TestCase): def setUp(self): self.mock_server = MockServer("localhost", 1337) self.socket_connection = SocketConnection("localhost", 1337) @sync async def test_sends_handshake_after_connect(self): await self.socket_connection.connect() self.assertTrue(self.mock_server.received_handshake())
- 通过Jupyter笔记本从GitHub安装模块
- 在Python中列出的string
- 在Python中重新分类一个实例
- 在matplotlib图中隐藏轴文本
- 为什么列表推导写入循环variables,但生成器不?
- 如何从matplotlib中删除帧(pyplot.figure vs matplotlib.figure)(frameon = matplotlib中的假问题)
- 对16331239353195370.0有特殊意义吗?
- 在TensorFlow中使用预先训练的单词embedded(word2vec或Glove)
- Python请求requests.exceptions.SSLError: _ssl.c:504:EOF违反协议