С чего обычно начинается тестирование API на Python и почему это нормально

Не позволяйте никому отговорить вас от requests и pytest. Тесты — это обычный Python, фикстуры комбинируются, parametrize аккуратно закрывает граничные значения, и код понятен всей команде. Для компактного набора тестов вокруг сервиса, за который вы отвечаете, это по-прежнему отличное решение.

Четыре вещи, которые превращаются в работу

  • Сессии. Фикстура с токеном работает ровно до того момента, когда токен истекает посреди прогона, — и вот вы уже пишете логику обновления и кешируете токен между воркерами.

  • Захваченные значения. Идентификатор из одного вызова уходит в следующий. Фикстуры справляются, пока граф не разрастётся, — а дальше поддерживать приходится уже сам граф фикстур.

  • Порядок выполнения. pytest не упорядочивает тесты по зависимостям. Команды берут плагины для сортировки, и одно раннее падение даёт целый экран красного, не имеющего отношения к делу.

Четвёртая — очистка при параллельном запуске. Как только в игру вступает pytest-xdist, удалять по шаблону имени становится небезопасно, и teardown должен точно знать, что именно создал каждый прогон.

Честная цена

По отдельности ничего из этого не сложно. Вместе они превращаются во внутренний фреймворк: conftest, который никто не хочет трогать, граф фикстур, понятный одному человеку, и набор тестов, который при покраснении отправляют в карантин, а не чинят. Такой исход случается достаточно часто, чтобы закладывать его в планы.

Что берёт на себя специализированный слой

Четыре вещи определяют, переживёт ли набор API-тестов свой первый год, каким бы инструментом вы ни пользовались: истекающие сессии, значения, которые существуют только во время выполнения, вызовы, зависящие друг от друга, и записи, которые никто не убирает. В TestSprite они называются Auto-Authentication, Dynamic Variables, Dependency Chains и Auto-Cleanup, а механику разбирает документация по тестированию API .

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

Если вы хотите, чтобы тесты лежали в вашем репозитории

Некоторым командам важно, чтобы набор тестов лежал в репозитории и проходил ревью как любой другой код, — это вполне разумное требование. CLI поддерживает бэкенд-тесты на Python, где зависимости объявляются для каждого теста, а teardown оформляется отдельным кейсом, так что граф задан явно, а не вытекает из вложенности фикстур. Подробности — в репозитории CLI.

Conftest, который съел команду

У набора тестов на Python, который перерос сам себя, есть узнаваемая форма, и живёт она в одном файле. Conftest начинается с фикстуры сессии и базового URL. Потом фикстура токена, потом кеш, чтобы токен не запрашивался на каждый тест, потом блокировка, потому что воркеры xdist устраивали гонки за этот кеш. Потом фабрики, потом реестр, чтобы teardown знал, что удалять, потом маркеры порядка, потому что одни фабрики зависят от других.

Каждый шаг по отдельности правильный, а в результате — шестьсот строк, которые понимает один человек. Верный признак: кто-то говорит, что тест падает, и первый вопрос — не менялся ли conftest.

Об этом стоит сказать прямо, потому что решения строить такой фреймворк никто никогда не принимает. Он накапливается сам. Решить заранее, какие части вы готовы поддерживать сами, а какие — нет, дешевле, чем выяснить ответ на девятом месяце.

Запускайте там, где меняется код

Набор тестов на Python, который запускается перед релизами, ловит проблемы поздно. Запуск на каждый pull request — вот изменение, которое действительно важно.

Есть два пути, и выбор на самом деле о том, кто владеет пайплайном. Подключение GitHub App из дашборда не требует вообще никаких изменений в репозитории, потому что он реагирует на деплой, который ваша сборка и так выпускает. Добавление шага GitHub Actions помещает проверку в репозиторий, где она проходит ревью наравне с остальной сборкой.

Что TestSprite берёт на себя, чтобы этим не занимались вы

Четыре вещи, которые превращают набор на pytest в инфраструктуру, решены здесь на уровне продукта. Auto-Authentication получает и обновляет сессии для каждого окружения. Dynamic Variables переносят идентификатор из одного ответа в следующий запрос. Dependency Chains выводят порядок выполнения из того, что каждому кейсу нужно и что он производит, поэтому падение пропускает зависимые кейсы, а не роняет их. Auto-Cleanup удаляет ровно то, что создал прогон, — именно это делает параллельный запуск безопасным.

Покрытие тоже приходит с другой стороны. API Discovery перечисляет эндпоинты, которые работающий сервис действительно предоставляет, а сгенерированные планы включают пути ошибок и граничные значения, которые в конце спринта никто не пишет.

В итоге получается набор тестов, который никто не боится менять, и никакого conftest, понятного одному человеку. Оставьте pytest для юнит-тестов и тестов логики, где он действительно лучший инструмент; речь о том, где живёт широта покрытия.

Стоит ли отказаться от pytest?

Нет. Оставьте его для тестов, которые ваша команда написала осознанно, особенно на уровне юнитов и логики. Спор идёт о том, где живёт широкое покрытие API.

А что насчёт httpx и async?

Удобнее для конкурентности — и те же четыре проблемы. Сессии, захваченные значения, порядок выполнения и очистка — это свойства тестирования сервиса с состоянием, а не HTTP-клиента.

Как быть с тестовыми данными?

Создавайте то, что нужно каждому тесту, и удаляйте ровно это, привязав к прогону. Общие предзаполненные данные выглядят удобно, а потом становятся причиной того, что тесты проходят только в определённом порядке.

Могут ли сгенерированное покрытие и pytest сосуществовать?

Да, и это обычная схема. Сгенерированное покрытие — для широты, pytest — для случаев, которые вы описали намеренно.

Python здесь всё ещё хороший выбор?

Да. Ограничение не в языке. Ограничение в том, сколько тестовой инфраструктуры вы готовы поддерживать сами.

Коротко

requests и pytest — правильный выбор, пока не появляется состояние.

Тестирование API на Python начинается прекрасно, а потом превращается во внутренний фреймворк вокруг сессий, захваченных значений, порядка выполнения и очистки. Оставьте pytest для того, что вы описали намеренно, а широту покрытия и четыре проблемы состояния отдайте специализированному слою.