Оригинальное видео:Strategies for testing Async code - PyCon 2019
Также см.:
Предыдущие статьи об асинхронном программировании:
- Асинхронное программирование 101: что это такое, попробуйте Python asyncio
- Асинхронное программирование 101: краткая история асинхронного ожидания Python
- Асинхронное программирование 101: Написание цикла событий
- Асинхронное программирование 101: циклы for в asyncio
- Асинхронное программирование 101: Расширенный asyncio, часть 1
Асинхронное программирование, по сути, обеспечивает параллелизм за счет сотрудничества, то есть:Когда требуется ожидание, то есть когда происходит ввод-вывод, управление передается основному циклу событий.(Управление выходом при «ожидании» асинхронных результатов.) Этот процесс немного похож на цикл событий, завершающий работу операционной системы. Вы можете думать о цикле событий как об операционной системе, а затем думать о сопрограмме как о потоке. Весь цикл событийв потоке, что означает, что переключение задач более эффективно без переключения контекста.
Асинхронный код очень эффективен, но у него есть и очень болезненное место — тестирование.
0x01 : экземпляр асинхронного теста
Давайте рассмотрим простой пример:Catкласс, естьmoveметод, этот метод является асинхронным.
Затем напишите тестовый класс с помощью unittest, можете ли вы найти проблему со следующим кодом?
herd(grafield, 'forward')Возврат - это объект сопрограммы (объект сопрограммы), если вы неawaitЕму ничего не будет. И объект сопрограммы правдив, поэтомуassertTrue()может пройти. Если вы запустите тест, вы увидитеcoroutine herd was nerver awaitedпредупреждение.
Назовите это такawaitпо-прежнему неправильно, потому чтоawaitКлючевые слова могут появляться только вasyncвнутри функции.
Одним из решений является присоединение к циклу событий:
Это может сработать, но я думаю, вы сами это видите, это очень хлопотно. Если у меня есть несколько методов, мне нужен каждыйtestВсе ли методы добавляют цикл обработки событий? Что еще более важно, я просто хочу провести некоторое модульное тестирование, цикл событий на самом деле является низкоуровневой деталью на данный момент, и меня это не должно волновать.
В Python 3.7 asyncio добавил новый метод:asyncio.run(), который скрывает от вас детали цикла обработки событий, поэтому вы можете сделать код более лаконичным:
0x02 pytest-asyncio
Установить:pip install pytest-asyncio, что на самом делеpytestплагин из .
Использование очень простое, важно то, что мы знаем, как это работает. Проблема с предыдущим кодом заключается в том, что средство запуска pytest по умолчанию обрабатывает все функции как обычные функции, в то время как для асинхронных функций при вызове возвращается объект сопрограммы. Итак, нам нужно найти способ сообщить pytest использовать цикл обработки событий для запуска тестового метода.
Один из способов — создать цикл обработки событий и внедрить его в тесты, например:
import asyncio
import pytest
async def say(what, when):
await asyncio.sleep(when)
return what
@pytest.fixture
def event_loop():
loop = asyncio.get_event_loop()
yield loop
loop.close()
def test_say(event_loop):
expected = 'This should fail!'
assert expected == event_loop.run_until_complete(say('Hello!', 0))
Неудобство этого метода заключается в том, что вам нужно каждый раз вручную внедрять цикл событий.Более элегантный метод — настроить средство запуска тестов так, чтобы оно распознавало асинхронные функции и выполняло их как асинхронные задачи.
pytest-asyncioГотовая функция вот такая, ее API очень простой, нужно только добавить асинхронную функцию@pytest.mark.asyncioМодификатор может быть:
import pytest
from say import say
@pytest.mark.asyncio
async def test_say():
assert 'Hello!' == await say('Hello!', 0)