Pytest-asyncio
pytest-asyncio запускает каждый тестовый элемент в своем собственном цикле событий [asyncio]. Доступ к циклу можно получить через фикстуру event_loop
, которая автоматически запрашивается всеми асинхронными тестами.
async def test_provided_loop_is_running_loop(event_loop):
assert event_loop is asyncio.get_running_loop()
Pytest-asyncio предоставляет два режима обнаружения тестов: строгий и автоматический.
Строгий режим
В строгом режиме pytest-asyncio будет запускать только тесты с маркером [asyncio] и оценивать только асинхронные фикстуры, декорированные @pytest_asyncio.fixture. Тестовые функции и фикстуры без этих маркеров и декораторов не будут обрабатываться pytest-asyncio.
Этот режим предназначен для проектов, которые хотят поддерживать несколько библиотек асинхронного программирования, поскольку он позволяет pytest-asyncio сосуществовать с другими плагинами для асинхронного тестирования в одной кодовой базе.
Pytest автоматически включает установленные плагины. В результате плагины [pytest] должны мирно сосуществовать в конфигурации по умолчанию. Вот почему строгий режим является режимом по умолчанию.
Автоматический режим
В автоматическом режиме pytest-asyncio автоматически добавляет маркер asyncio ко всем функциям асинхронного тестирования. Он также станет владельцем всех асинхронных фикстур, независимо от того, декорированы ли они символами @pytest.fixture или @pytest_asyncio.fixture.
Этот режим предназначен для проектов, которые используют [asyncio] в качестве единственной библиотеки асинхронного программирования. Автоматический режим обеспечивает простейшую настройку теста и приспособления и является рекомендуемым значением по умолчанию.
Если вы собираетесь поддерживать несколько библиотек асинхронного программирования, например, [asyncio] и [trio], предпочтительным вариантом будет строгий режим.
Конфиг
# pytest.ini
[pytest]
asyncio_mode = auto
pytest tests --asyncio-mode=strict
для выбора режима
Fixtures
event_loop
создает новый асинхронный цикл событий на основе текущей политики цикла событий. Новый цикл доступен как возвращаемое значение этой фикстуры или через asyncio.get_running_loop
. Цикл событий завершается, когда область действия фикстуры заканчивается. Область фикстуры по умолчанию ограничивается областью видимости функции.
def test_http_client(event_loop):
url = "http://httpbin.org/get"
resp = event_loop.run_until_complete(http_client(url))
assert b"HTTP/1.1 200 OK" in resp
Обратите внимание, что при использовании фикстуры event_loop
вам необходимо взаимодействовать с циклом событий, используя такие методы, как event_loop.run_until_complete
. Если вы хотите ожидать код внутри своей тестовой функции, вам нужно написать сопрограмму и использовать ее в качестве тестовой функции. Маркер asyncio используется для обозначения сопрограмм, которые следует рассматривать как тестовые функции. Фикстура event_loop
может быть переопределена в любом из стандартных местоположений [pytest], например, непосредственно в тестовом файле или в conftest.py
. Это позволяет переопределить область видимости:
@pytest.fixture(scope="session")
def event_loop():
policy = asyncio.get_event_loop_policy()
loop = policy.new_event_loop()
yield loop
loop.close()
Если вам нужно изменить тип цикла событий, необходимо установить пользовательскую политику цикла событий, а не переопределять фикстуру event_loop
.
Если декоратор pytest.mark.asyncio
применяется к тестовой функции, фикстура event_loop
будет автоматически запрашиваться тестовой функцией.
Другие готовые фикстуры: unused_tcp_port
, unused_tcp_port_factory
, unused_udp_port
and unused_udp_port_factory
.
Markers
Сопрограмма или асинхронный генератор с маркером pytest.mark.asyncio
будут рассматриваться [pytest] как тестовая функция. Отмеченная функция будет выполняться как асинхронная задача в цикле событий, предоставленном фикстурой event_loop
. Чтобы сделать ваш тестовый код немного более лаконичным, можно использовать фичу [pytest] pytestmark
для маркировки целых модулей или классов этим маркером. Будут затронуты только тестовые сопрограммы (по умолчанию сопрограммы с префиксом test_).
import asyncio
import pytest
# All test coroutines will be treated as marked.
pytestmark = pytest.mark.asyncio
async def test_example(event_loop):
"""No marker!"""
await asyncio.sleep(0, loop=event_loop)
В автоматическом режиме маркер pytest.mark.asyncio
можно не указывать, маркер автоматически добавляется в функции асинхронного тестирования.
Decorators
Асинхронные фикстуры определяются так же, как и обычные фикстуры pytest, за исключением того, что они должны быть декорированы @pytest_asyncio.fixture.
import pytest_asyncio
@pytest_asyncio.fixture
async def async_gen_fixture():
await asyncio.sleep(0.1)
yield "a value"
@pytest_asyncio.fixture(scope="module")
async def async_fixture():
return await asyncio.sleep(0.1)
Поддерживаются все области, но если вы используете область за границами функции, вам потребуется переопределить фикстуру event_loop
, чтобы она имела ту же или более широкую область. Для асинхронных фикстур требуется цикл обработки событий, поэтому они должны иметь ту же или более узкую область действия, чем фикстура event_loop
. автоматический режим автоматически преобразует асинхронные фикстуры, объявленные с помощью стандартного декоратора @pytest.fixture, в асинхронные версии.
Смотир еще:
- документация
- [pytest]
- [pytest-bdd]
- pytest-asyncio has a closed event loop, but only when running all tests
- aioresponses is a helper to mock/fake web requests in python [aiohttp] package
- [asyncio]
- [модульные-тесты]