Python 3.4 asyncio
ライブラリを使用してコードの単体テストを記述する最良の方法は何ですか?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())
このテストケースをデフォルトのテストランナーで実行すると、メソッドは最初のyield from
命令までしか実行されず、その後アサーションを実行する前に戻るため、テストは常に成功します。これにより、テストは常に成功します。
このような非同期コードを処理できるビルド済みのテストランナーはありますか?
トルネードの 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
J.F. Sebastianが提案したように、このデコレーターはテストメソッドコルーチンが終了するまでブロックします。これにより、次のようなテストケースを作成できます。
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())
このソリューションは、おそらくいくつかのEdgeケースを見逃しています。
このような機能をPythonの標準ライブラリに追加して、asyncio
とunittest
の相互作用をすぐに使えるようにする必要があると思います。
_async_test
_(Marvin Killingによって提案された)は、間違いなく役立ちます-直接呼び出しloop.run_until_complete()
しかし、すべてのテストで新しいイベントループを再作成し、API呼び出しにループを直接渡すことも強くお勧めします(少なくともasyncio
自体は、それを必要とするすべての呼び出しで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())
_
テストケース内のテストを分離し、_test_a
_で作成されたが__test_b
_の実行時にのみ終了する長年のコルーチンのような奇妙なエラーを防ぎます。
pytest-asyncio 有望に見えます:
@pytest.mark.asyncio
async def test_some_asyncio_code():
res = await library.do_something()
assert b'expected result' == res
本当にasync_test
https://stackoverflow.com/a/23036785/350195 に記載されているラッパー、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())
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
を使用することもできます。これは@Andrew Svetlov、@ Marvin Killingの回答と同様のアプローチを取り、使いやすい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)
ご覧のとおり、非同期ケースはAsyncTestCase
によって処理されます。同期テストもサポートしています。 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)
通常、非同期テストをコルーチンとして定義し、デコレーターを使用して「同期」します。
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())