Тестирование асинхронного кода требует специальных подходов, отличающихся от тестирования синхронного кода. Рассмотрим основные методы и инструменты.
Плагин pytest-asyncio
позволяет тестировать корутины напрямую.
pip install pytest-asyncio
Пример теста:
import pytest
import asyncio
@pytest.mark.asyncio
async def test_async_code():
async def async_func():
await asyncio.sleep(0.1)
return 42
result = await async_func()
assert result == 42
Для тех, кто предпочитает unittest:
import unittest
class TestAsync(unittest.IsolatedAsyncioTestCase):
async def test_async(self):
async def async_func():
return "result"
self.assertEqual(await async_func(), "result")
Использование AsyncMock для мокирования:
from unittest.mock import AsyncMock
import pytest
@pytest.mark.asyncio
async def test_async_mock():
mock = AsyncMock(return_value=42)
result = await mock()
assert result == 42
mock.assert_awaited_once()
Пример с aiohttp:
import pytest
from aiohttp import web
async def hello(request):
return web.Response(text="Hello")
@pytest.fixture
async def test_client(aiohttp_client):
app = web.Application()
app.router.add_get("/", hello)
return await aiohttp_client(app)
@pytest.mark.asyncio
async def test_hello(test_client):
resp = await test_client.get("/")
assert resp.status == 200
text = await resp.text()
assert text == "Hello"
import pytest
@pytest.mark.asyncio
async def test_parallel():
async def coro(x):
return x * 2
results = await asyncio.gather(
coro(1),
coro(2),
coro(3)
)
assert results == [2, 4, 6]
@pytest.mark.asyncio
async def test_async_exception():
async def raise_exc():
raise ValueError("Error")
with pytest.raises(ValueError, match="Error"):
await raise_exc()
@pytest.mark.asyncio
async def test_timeout():
async def long_running():
await asyncio.sleep(10)
return "OK"
with pytest.raises(asyncio.TimeoutError):
await asyncio.wait_for(long_running(), timeout=0.1)
@pytest.fixture
async def async_fixture():
await asyncio.sleep(0.1)
return "fixture value"
@pytest.mark.asyncio
async def test_with_async_fixture(async_fixture):
assert async_fixture == "fixture value"
@pytest.mark.asyncio
async def test_event():
event = asyncio.Event()
async def set_event():
await asyncio.sleep(0.1)
event.set()
asyncio.create_task(set_event())
await event.wait()
assert event.is_set()
async def test_queue():
queue = asyncio.Queue()
await queue.put("item")
assert await queue.get() == "item"
from unittest.mock import patch
async def external_call():
return "real"
@pytest.mark.asyncio
@patch("__main__.external_call", new_callable=AsyncMock)
async def test_patched(mock_call):
mock_call.return_value = "mocked"
assert await external_call() == "mocked"
await
в тестахpytest-asyncio
- основной плагинasynctest
- альтернативный подход (устаревает)aiohttp
- для тестирования HTTP-серверов/клиентовaiomysql/aiopg
- для тестирования с БДДля эффективного тестирования асинхронного кода:
pytest-asyncio
или unittest.IsolatedAsyncioTestCase
AsyncMock
для мокированияПравильно написанные тесты асинхронного кода помогают выявлять проблемы с race conditions, deadlock'ами и другими асинхронными специфичными багами.