НОВОСТИ

Запускает одну pytest-проверку поведения для обычной, асинхронной и генераторной форм одного шаблона Python-функции.

На этой странице 3 раздела

Python-функции могут выполняться по-разному. Обычная функция сразу возвращает результат, асинхронная завершает работу после await, а генератор выдаёт значения во время перебора. Декораторам и другим обёрткам часто нужно поддерживать все три варианта. Если скопировать один тест трижды, проверки легко начнут расходиться.

pytest.mark.parametrize запускает несколько случаев, а pytest-asyncio и AnyIO — асинхронные тесты. Но ни один из них не создаёт и не вызывает три вида функций из одного исходного шаблона.

transtests — небольшая Python-библиотека и плагин для pytest. Она превращает один шаблон функции в обычную, асинхронную и генераторную формы, а затем запускает одну и ту же проверку для каждой. Pytest показывает три результата отдельно, поэтому причину сбоя легко найти. Релиз доступен на GitHub.

Параметризация по типу функции

После установки pytest автоматически обнаруживает фикстуру transformed — подготовленный объект, который тест получает как аргумент. Любой запросивший её тест превращается в три отдельно отображаемых случая: обычный, асинхронный и генераторный.

from asyncio import run
from inspect import iscoroutinefunction, isgeneratorfunction

def test_addition(transformed):
    @transformed
    def add(left, right):
        return left + right

    if iscoroutinefunction(add):
        assert run(add(1, 2)) == 3
    elif isgeneratorfunction(add):
        assert list(add(1, 2)) == [3]
    else:
        assert add(1, 2) == 3

Pytest запускает этот тест трижды и отдельно отображает каждый вид функции.

Fixture возвращает декоратор. Для асинхронного и генераторного случаев transfunctions строит варианты из того же шаблона. Общие assertions остаются в одном тесте, а pytest сохраняет отдельные ID случаев.

У генераторного режима есть важное ограничение: он преобразует явные markers вроде yield_from_it, но не превращает каждый обычный return в yield. Поэтому в шаблоне должна быть настоящая генераторная точка. Асинхронные генераторы не поддерживаются.

Сравнение с похожими инструментами

Обычная параметризация pytest лучше, когда sync- и async-API реализованы независимо, а не созданы из одного шаблона.

pytest-asyncio и плагин AnyIO для pytest позволяют запускать асинхронные тесты в разных окружениях, а transtests меняет сам вид функции. Эти инструменты дополняют друг друга.

Hypothesis меняет входные данные, а transtests — вид тестируемой функции: обычный, асинхронный или генераторный. unasync преобразует целые деревья исходников и лучше подходит для поддержки отдельных синхронных и асинхронных API.

Для кого предназначен transtests

transtests прежде всего нужен авторам декораторов, систем регистрации функций, диспетчеров вызовов, промежуточных обработчиков и других инструментов, которые меняют поведение функций. Им часто важно не только то, что вернула переданная функция, но и момент, когда её тело действительно выполнилось.

Вместе с LockTraceWrapper один тест может проверить, что тело переданной функции действительно выполнилось под блокировкой, а не просто был создан ещё не запущенный асинхронный объект или генератор. Такая проверка легко встраивается в более крупный набор тестов.