Почему автоматизация API буксует в одних и тех же четырёх местах

Все команды начинают одинаково. Кто-то пишет запрос к одному эндпоинту, проверяет код ответа и добавляет это в CI. Работает. Потом набор тестов растёт — и появляются четыре проблемы, причём примерно в одном и том же порядке у всех.

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

Ничего экзотического здесь нет. Так устроен любой настоящий бэкенд. Разница между набором тестов, который живёт долго, и тем, который удаляют через квартал, — в том, заложили эти четыре вещи в архитектуру заранее или столкнулись с ними по ходу дела.

Начните с того, что сервис действительно отдаёт наружу

Но до всего этого нужна инвентаризация. Тесты, написанные по памяти, покрывают те эндпоинты, о которых вы вспомнили, — а это обычно те, что и так работали.

Реальную картину дают два источника. Спецификация — файл OpenAPI или документ с требованиями к продукту — говорит, что должно существовать. Проход обнаружения по работающему сервису показывает, что существует на самом деле. Расхождение между ними интересно само по себе и служит куда лучшей отправной точкой для решений о покрытии, чем чистый лист. В TestSprite этот первый шаг называется API Discovery, и на вход он принимает PRD, документацию API или наблюдение за реальными вызовами.

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

Проблема первая: аутентификация (Auto-Authentication)

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

Вместо этого учётные данные должны принадлежать окружению, а не отдельному тесту, а вход в систему должен выполняться как часть прогона. В TestSprite это называется Auto-Authentication: вы один раз настраиваете сценарий входа, и перед каждым прогоном запрашивается свежий токен. Два принципа здесь важнее, чем кажется.

  • Отделите учётные данные от теста. Тест, внутри которого лежит пароль, нельзя ни передать коллеге, ни направить на второе окружение, ни закоммитить. В сценарии должно быть написано «от имени авторизованного пользователя», а не «от имени user@example.com с таким-то паролем».

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

Проблема вторая: значения, которые существуют только во время выполнения (Dynamic Variables)

Вызов создания возвращает идентификатор. Вызовам обновления и удаления он нужен. Если идентификатор вставлен в тест руками, тест действителен ровно один раз.

Решение — забрать значение из ответа и сослаться на него дальше, чтобы набор передавал данные по цепочке, а не строил догадки. На практике это значит, что в тесте не написано DELETE /orders/8412. В нём написано: удалить заказ, который создал этот прогон, каким бы ни оказался его идентификатор.

На бумаге это звучит очевидно — и при этом остаётся самой частой причиной, по которой набор, проходивший в прошлом месяце, сегодня падает. В TestSprite у механизма есть название, Dynamic Variables: взять значение из одного ответа и подставить его в следующий запрос, причём связи выводятся автоматически, а не поддерживаются руками.

Проблема третья: эндпоинты, зависящие друг от друга (Dependency Chains)

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

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

Явное описание зависимостей решает обе. Каждый сценарий объявляет, что ему нужно и что он производит, прогон выстраивает по этому безопасный порядок, и при сбое вы получаете одно падение и девятнадцать пропущенных сценариев вместо двадцати красных строк, которые надо прочитать. В этом и разница между Endpoint Tests, которые покрывают по одному маршруту за раз, и Integration Tests — многошаговыми цепочками, где ответ одного шага питает следующий.

Проблема четвёртая: данные, которые остаются после вас (Auto-Cleanup)

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

Очистка должна быть частью прогона и привязываться к ресурсам, которые создал именно этот прогон. Очистка по шаблону, когда удаляется всё, чьё имя начинается с test-, работает до тех пор, пока два прогона не пересекутся во времени или пока кто-нибудь из бизнеса не заведёт запись с неудачным именем. Auto-Cleanup удаляет созданные прогоном записи по его завершении — и именно такой вариант выживает при параллельном выполнении.

Как выглядит работающая автоматизация API от начала до конца

Если собрать все четыре пункта вместе, схема, которая продержится год, выглядит так.

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

Встраивание в пайплайн

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

Что стоит знать до выбора: интерфейса два, и решают они эту задачу по-разному. Веб-портал — путь «сгенерировать и поддерживать», где обнаружение, связывание зависимостей и очистка выводятся за вас. CLI — путь «со своим кодом»: бэкенд-тесты здесь пишете на Python вы сами, сами объявляете, что каждому из них нужно и что он производит, а очистка — это тест, который вы соответствующим образом помечаете. Те же четыре проблемы, другое разделение труда. Команды, которым нужно, чтобы набор жил в их репозитории, выбирают второй путь осознанно.

Два пути, и выбор в том, где должна жить конфигурация.

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

Два честных ограничения

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

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

Как TestSprite решает эти четыре проблемы

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

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

Что это вам даёт: набор тестов, который продолжает работать по мере изменений API, без conftest и вспомогательной директории, в которой разбирается один человек. Нагрузочная способность и сканирование уязвимостей остаются отдельными инструментами.

Нужна ли спецификация OpenAPI, чтобы начать?

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

Чем это отличается от коллекции сохранённых запросов?

Коллекция хранит запросы. Четыре проблемы из этой статьи — про состояние между запросами: сессия, захваченные значения, порядок и очистка. Коллекцию можно научить всему этому, если написать достаточно скриптов, — а это ровно та нагрузка на поддержку, от которой большинство команд и пытается уйти.

Можно ли тестировать API, недоступный извне?

Уточните, о какой стороне речь. CLI умеет пробрасывать туннель к приложению, запущенному на вашей машине, так что фронтенд на localhost покрывается. Этот локальный режим работает только с фронтендом, поэтому бэкенд-сервису, недоступному из интернета, для прогонов API всё равно нужен доступный URL. Стоит проверить это на вашем стенде, прежде чем планировать внедрение.

А как быть с API за входом с многофакторной аутентификацией?

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

Сколько эндпоинтов должен покрывать первый набор тестов?

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

У каждой из этих четырёх тем есть своя страница в документации по тестированию API , если вам нужна механика, а не рассуждения.

Коротко

Продумайте сессию, состояние, порядок и очистку с первого дня.

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