web-dev-qa-db-ja.com

テスト方法Python 3.4 asyncioコード?

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命令までしか実行されず、その後アサーションを実行する前に戻るため、テストは常に成功します。これにより、テストは常に成功します。

このような非同期コードを処理できるビルド済みのテストランナーはありますか?

65
Marvin Killing

トルネードの 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の標準ライブラリに追加して、asynciounittestの相互作用をすぐに使えるようにする必要があると思います。

49
Marvin Killing

_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_の実行時にのみ終了する長年のコルーチンのような奇妙なエラーを防ぎます。

46
Andrew Svetlov

pytest-asyncio 有望に見えます:

@pytest.mark.asyncio
async def test_some_asyncio_code():
    res = await library.do_something()
    assert b'expected result' == res
13
ostrokach

本当にasync_testhttps://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())
10
peralmq

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)
6
pylover

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)
2
kwarunek

通常、非同期テストをコルーチンとして定義し、デコレーターを使用して「同期」します。

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())
1
jcazor