Асинхронное программирование 101: Как тестировать асинхронный код

Python

Оригинальное видео:Strategies for testing Async code - PyCon 2019

Также см.:

Предыдущие статьи об асинхронном программировании:

Асинхронное программирование, по сути, обеспечивает параллелизм за счет сотрудничества, то есть:Когда требуется ожидание, то есть когда происходит ввод-вывод, управление передается основному циклу событий.(Управление выходом при «ожидании» асинхронных результатов.) Этот процесс немного похож на цикл событий, завершающий работу операционной системы. Вы можете думать о цикле событий как об операционной системе, а затем думать о сопрограмме как о потоке. Весь цикл событийв потоке, что означает, что переключение задач более эффективно без переключения контекста.

Асинхронный код очень эффективен, но у него есть и очень болезненное место — тестирование.

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)