[python] 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명령 까지만 실행되고 그 후에는 어설 션을 실행하기 전에 반환 되므로 테스트는 항상 성공 합니다. 이로 인해 테스트가 항상 성공합니다.

이와 같은 비동기 코드를 처리 할 수있는 미리 빌드 된 테스트 실행기가 있습니까?



답변

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

이 솔루션은 아마도 일부 엣지 케이스를 놓칠 수 있습니다.

나는이 같은 시설이 파이썬의 표준 수 있도록 라이브러리에 추가해야한다고 생각 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실행 시간 에만 완료된 오랜 코 루틴과 같은 이상한 오류를 방지 합니다.


답변

Python 3.8 unittest 에는 이러한 목적으로 설계된 IsolatedAsyncioTestCase 함수 가 함께 제공됩니다 .

from unittest import IsolatedAsyncioTestCase

class Test(IsolatedAsyncioTestCase):

    async def test_functionality(self):
        result = await functionality()
        self.assertEqual(expected, result)


답변

pytest-asyncio 는 유망 보입니다.

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


답변

https://stackoverflow.com/a/23036785/350195 에서 async_test언급 된 래퍼 와 정말 비슷합니다 . 여기에 Python 3.5 이상에 대한 업데이트 된 버전이 있습니다.

def async_test(coro):
    def wrapper(*args, **kwargs):
        loop = asyncio.new_event_loop()
        try:
            return loop.run_until_complete(coro(*args, **kwargs))
        finally:
            loop.close()
    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)

편집 1 :

중첩 테스트에 대한 @Nitay 답변을 참고하십시오 .


답변

당신은 또한 사용할 수 있습니다 aiounittest그 답을 죽이는 @Marvin, 앤드류 Svetlov와 유사한 접근 방식을 취하고 및 사용에 쉽게에서 포장 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)