Представляем transtests: один тест для обычных, асинхронных и генераторных функций.
Запускает одну 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 один тест может проверить, что тело переданной функции действительно выполнилось под блокировкой, а не просто был создан ещё не запущенный асинхронный объект или генератор. Такая проверка легко встраивается в более крупный набор тестов.