Привет, Хабр! Меня зовут Федор Филиппов, я тестировщик команды разработки процессинговых систем в компании «Передовые Платежные Решения». Недавно мы столкнулись с необходимостью основательно подойти к автоматизации тестов. В этой статье я расскажу, как мы разработали универсальный автогенератор тестов, который, как нам кажется, подходит для любого сервиса API. Ну и, конечно, расскажу, как он работает и как мы его планируем развивать.
Разработка автогенератора вел я совместно с командой, поэтому в дальнейшем описании  буду использовать местоимение «мы», а в некоторых местах  делиться  мнением от первого лица

Предпосылки для создания

Идеи автоматизировать все и везде витают в воздухе у любой IT-компании, и наша не исключение. Как и в любой истории, у нас есть отправная точка — это история разработки сервиса, который обеспечивает контроль терминальной сети компании. Мы называем его TRS — Terminal Registry Service. TRS вмещает в себя довольно широкий функционал, его использует многие службы и команды, так что сервис оброс большим количеством эндпоинтов — их сейчас насчитывается 66.

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

Сама бизнес-логика в целом понятна и  проста. На один эндпоинт, как правило, достаточно сделать одну проверку, иногда с предварительной подготовкой базы данных (далее БД). Что касается валидации полей, с нашим количеством эндпоинтов полное покрытие проверки требует 2000+ комбинаций запросов. Сюда включена проверка граничных значений, вставка различных типов данных, проверка обязательности полей, использование значения NULL и т.д., все по классике. Некоторые запросы иногда повторялись, так как их методы тоже повторялись, и в таких кейсах были определенные сходства.

Для автотестов мы использовали jMeter и комбинации запросов брали из  CSV-файлов, а их необходимо было предварительно разработать и корректно заполнить. Иногда также требовалась определенная согласованность с теми данными, которые есть или должны быть в БД.  Так что разработка и заполнение CSV-файлов выливается в довольно большой объем ручной работы., поскольку файлы эти особо не переиспользуемы.

От эндпоинта к эндпоинту создается впечатление, что действия начинают повторяться. Все активнее копипастишь, все чаще смотришь в одну и ту же документацию – Swagger. Тут-то и зарождается мысль: «А может ли кто-то другой смотреть туда? И выписывать необходимые данные…». Можно сразу задуматься об ИИ, и, скорее всего, это хорошая идея. Но мы сторонники последовательного развития: сначала изучим, что «поближе».

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

Ставим конкретные задачи

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

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

1. Создать универсальный инструмент для проверки любого API. API могут быть разные, но их все объединяет Swagger. Инструмент, умеющий работать с документацией в Swagger, может быть легко использован для любого API.

2. Сократить время на работу с однотипными тестами. Проверка валидации полей всегда и везде одинаковая. Давайте делать это автоматически, долой рутину! Вот с бизнес-логикой мы еще повозимся: там больше уникальности, а простора для автоматической генерации мало, поэтому пока будем писать автотесты вручную.

3. Сделать удобный инструмент разработчика для экспресс-проверки. Почему бы таким инструментом не пользоваться разработчикам? Удобно, быстро, не нужно ничего передавать тестировщикам и ждать ответа. Экономим время и ресурсы.

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

Окунувшись в мир автогенерации тестов, мы нашли довольно много инструментов, библиотек и утилит. Среди них библиотека faker, утилиты swagger_meqa, swagger-test и многие другие. Взвесив все находки, мы решили остановиться на более-менее простых инструментах. Сердцем нашего автогенератора стал инструмент Schemathesis.

Собрали в таблицу  критичные для нас моменты и  весомые аргументы для выбора Schemathesis.

Важные для нас критерии

Плюсы Schemathesis

Использование актуального для нас языка программирования. Не хотелось бы сильно расширять стек для разработки в рамках команды.

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

Наличие возможности работы со Swagger (OpenAPI Doc). Для нас это база.

Самостоятельно работает со Swagger (OpenAPI Doc).

Отсутствие необходимости описывать модели генерации данных и тестов. Это  трудоемкий процесс, который потребовал бы больше времени и ресурсов команды.

Генерирует данные через библиотеку property-based Hypothesis. Его работа построена как раз на описании требований к генерируемым данным, которые мы берем из Swagger.

Сам Schemathesis, спасибо разработчикам, очень гибок в настройках, и мы можем использовать его по-разному:

- целиком самостоятельно — хоть это и потребует дополнительных знаний библиотеки,

- через HOOK — чтобы более гибко настраивать работу библиотеки под наши нужды,

- подключить как библиотеку к фреймворку Pytest - это было бы неплохо, учитывая потенциальную перспективу развития инструмента

- возможность без всяких доработок встроить Schemathesis в CI/CD.

В общем, кругом будто бы одни плюсы, и Schemathesis — отличный выбор. В поисках того, во что бы его быстро и удобно завернуть, мы остановились на jMeter. Его у нас используют многие команды.

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

Как работает наш автогенератор тестов

Учитывая, что аудитория потенциальных пользователей автогенератора имеет разные компетенции, мы предусмотрели несколько вариантов запуска:

Запуск через BAT-файл. Максимально простой вариант: запустили, вставили ссылку, интерактивно прокликали поля, отправили, подождали, быстро получили результат. При необходимости сделали выводы, исправили, перезапустили. Вариант подходит всем: скачиваем необходимые файлы из инструкции и пользуемся.

Запуск в jMeter, минуя запуск BAT-файла — вариант «для продвинутых». Если хотим заглянуть «под капот», то запускаем jMeter, выбираем нужный сценарий, вставляем ссылку на документацию и пользуемся. Видим чуть больше и онлайн, в привычном интерфейсе.

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

Пример запуска через BAT-файл
Пример запуска через BAT-файл

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

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

Первый этап работы

Начнем с первого стейджа. Для запуска автогенератора потребуется только ссылка на Swagger Doc. По ней доступна документация всего API в виде json, и именно от него мы будем отталкиваться. Важно обратить внимание, что такой документ должен быть заполнен в соответствии с инструкцией и требованиями разработчика Swagger OpenApi. А учитывая, что в большинстве случаев используются утилиты, которые это делают автоматически, проблем с этим обычно не бывает.

Независимо от того, запускаем ли мы в jMeter или через BAT-файл, основные модули нашего автогенератора — это jMeter со сценарием (.jmx), Schemathesis и Runner. С первыми двумя в целом понятно, но что это за раннер вдруг возник?

Здесь все просто — нам не удалось с ходу подружить jMeter и Schemathesis. Как показала дальнейшая разработка, добавить прослойку в виде раннера оказалось отличным решением: она позволит при необходимости уйти от jMeter. Раннер написали на Python, так что использование, например, Pytest потенциально тоже возможно.

Общий пайплайн работы автогенератора
Общий пайплайн работы автогенератора

На схеме выше первые пять областей — это первый стейдж, последняя — второй. Пайплайн понятен из схемы, поясним лишь  некоторые особенности.

Первое, что встречает пользователя при запуске автогенератора, — это базовые настройки. В основном они реализованы через вкладку jMeter Настраиваемые параметры (User Defined Variables):

SWAGGER_PATH — ссылка на документацию OpenAPI / Swagger.

MAIN_DIR — директория, в которую будут выгружаться отчеты. По умолчанию они выгружаются рядом с JXM/BAT, но путь можно изменить. Это делают редко.

STUB_URL — адрес заглушки для работы Schemathesis (по умолчанию http://127.0.0.1:8888). Он нужен, чтобы не переполнить запросами рабочий API.

CASE_MODE — режим работы генератора (positive / negative / both): только позитивные тесты, только негативные или оба типа.

ONLY_METHOD — фильтрация по методу (например, только get).

ONLY_PATH — фильтрация по пути.

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

REQUEST_TIMEOUT — время таймаута работы с заглушкой при генерации кейсов в секундах.

PROD_TIMEOUT — время таймаута работы с боевыми запросами в секундах.

BEARER_TOKEN — токен авторизации. Если отсутствует, то настройка не используется.

GEN_MODE — режим генерации тестовых данных (true / false), то есть первый этап работы генератора.

EXEC_MODE — режим направления боевых запросов (true / false), то есть второй этап. Если выбран только он, то в API отправляются уже сгенерированные файлы.

PROD_SCHEME — принудительная установка необходимого протокола при формировании http-запросов в API. Она осталась с ранних стадий разработки, вероятно, мы скоро ее автоматизируем.

Пожалуй, некоторые из настроек стоит пояснить, и начну я со STUB_URL.  Недавно Schemathesis обновился и стал корректно работать с актуальными версиями Swagger OpenAPI. Но из Schemathesis убрали возможность запускать его в режиме -dry run, то есть без отправки запросов в тестируемый API.  Жаль, ведь это было бы для нас очень удобно,  мы именно так и планировали его использовать: передаем в Schemathesis документацию Swagger, получаем сгенерированные данные, а  тесты уже делаем в jMeter.

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

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

Далее идет группа настроек фильтров с приставкой ONLY_*** — это фильтры, которые позволяют сгенерировать тесты не на все эндпоинты, описанные в Swagger. Все-таки бывают ситуации, когда нет смысла перепроверять все.

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

GEN_MODE и EXEC_MODE — это как раз включение и выключение первого и второго стейджей.

Теперь, когда мы разобрались, самое время нажать «пуск». «Компуктер» зашуршал, логи побежали. Сначала автогенератор создает необходимое окружение и сам ставит Schemathesis — это при первом запуске.

Далее автогенератор начинает подробно изучать документацию Swagger и первым делом составляет набор кейсов. Это своего рода каркасы запросов в соответствии с эндпоинтами в документации, с учетом используемых в настройках фильтров. Если мы имеем 66 эндпоинтов, то и таких «каркасов» у нас соберется 66.

Часть лога с процессом генерации cases.json
Часть лога с процессом генерации cases.json

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

Пример содержания cases.json
Пример содержания cases.json

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

Обратим внимание — на этом этапе мы делаем еще один важный для нас шаг. Помимо разметки тестовых данных как POSITIVE и NEGATIVE, мы подключаемся через  HOOK и передаем operationId перед генерацией данных. Это важный параметр в Swagger-документации, который в дальнейшем позволит нам понять, для какого из кейсов генерировались данные. OperationId всегда есть у любого эндпоинта, и сломать это значение руками будет непросто. Так что он точно будет уникальный и окажется и в вашей документации.

Пример лога работы Schemathesis и передачи operationId через HOOK
Пример лога работы Schemathesis и передачи operationId через HOOK

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

Такая опция у Schemathesis имеется. Если приглядеться, в логе можно увидеть Test Phases — это параметр, который включает или выключает некоторые режимы генерации данных. Мы в своей работе остановились на двух режимах, и добавление третьего и четвертого значительно увеличит количество сгенерированных данных. Все подробности есть в документации Schemathesis, мы расскажем о режимах коротко:

  • Example — генерирует запросы из примеров, которые заполнены в Swagger.

  • Coverage — генерирует запросы для максимального покрытия тестами, в том числе граничные значения, различные типы данных и т. д.

  • Fuzzing — генерация в целом случайных значений в большом количестве.

  • Stateful – что-то вроде проверки состояния данных направленного ранее запроса. Например, мы сначала отправляем POST-запрос, а затем GET-запрос, чтобы проверить, сохранилась ли информация после POST. Но такие кейсы должны быть описаны в документации, чтобы данный режим работал.

Пример лога с отображением используемых test phazes и количеством сгенерированных test cases (тестовые данные).
Пример лога с отображением используемых test phazes и количеством сгенерированных test cases (тестовые данные).

Да, мы видим много ошибок в логе, но мы же направляли запросы в заглушку! Магия в том, что данные на основании нашего Swagger хоть и направлены в заглушку, но у нас сгенерировались, и теперь мы можем использовать их дальше.

Сохранились данные в двух HAR-отчетах. Теперь мы их аккуратно складываем в один большой файл test_data.json — это второй важный артефакт. При этом мы можем отфильтровать ненужные нам кейсы. Это еще одна особенность Schemathesis: в процессе генерации данных он генерирует все что может, даже если мы ставили какие-либо дополнительные флаги на ограничение проверок. Обычно он просто не направляет такой запрос, но данные, повторимся, сгенерирует.

Вот тут operationId нам как раз и поможет отфильтровать лишнее. Поскольку мы хотели проверить именно валидацию полей, то проверки, например, неправильных методов пока не нужны. Автогенератор сможет увидеть неактуальные для нас кейсы и просто не включит их в итоговый файл test_data.json. Изящно и практично.

Пример содержания файла test_data.json
Пример содержания файла test_data.json

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

По итогам работы первого стейджа мы получаем два важных артефакта — cases.json и test_data.json. Смело в них заглядываем, смотрим кейсы и редактируем, если требуется. А если нет, то переходим сразу во второй стейдж.

Второй этап

Во втором стейдже всё, пожалуй, проще. Напомним, что мы хотели чуть более управляемо направлять запросы в тестируемый API и чтобы это делал не сам Schemathesis. Поэтому автогенератор использует ранее сгенерированные данные из артефактов cases.json и test_data.json. jMeter это умеет делать отлично, тут ему особо ничего объяснять не пришлось. Мы лишь вывели в лог информацию о запросе, ответе и результате тестового запроса.

Пример лога запросов, направляемых в тестовый API
Пример лога запросов, направляемых в тестовый API

Говоря о результате, хотелось бы отметить еще один важный нюанс. Какой ответ считать успешным? Просто использовать код ответа будет не совсем корректно, и вот почему. Допускаем, что так может быть не везде, но, как правило, валидация полей происходит до обработки запроса в сервисе API.

Это значит, что запрос может пройти валидацию, но при этом мы получим отказ с кодом 404 или 409. Хотя данные, которыми мы заполнили поля, валидны. Пока мы решили тут ничего не усложнять и сделали довольно простую политику соответствия. В текущей реализации ожидается, что негативный кейс получит ответ с кодом 400 или 5хх. Для позитивных кейсов ожидается ответ с кодом НЕ 400 или НЕ 5хх. Это мы еще обязательно додумаем и доработаем.

Наш автогенератор заканчивает второй стейдж, а с ним и заканчивается и работа автогенератора в целом. Мы видим наглядное дерево запросов с сабрезультатами / subresults в интерфейсе jMeter. Открываем View Results Tree и видим, что все кейсы зеленые. Или не все — но в любом случае наглядно, с указанием тестового кейса и тестовых данных. Изучаем и делаем выводы.

Да, пока случаются «мусорные кейсы», но их легко отловить и либо удалить, либо подставить другие данные, которые потребуются для тестирования. А чтобы все было еще проще, мы собираем краткий отчет post_summary.txt. Это третий важный артефакт, который содержит в себе информацию о кейсах, помеченных как FAILED. И конечно, результат работы автогенератора был бы неполон без полноценного отчета report.json. Этот артефакт содержит всю информацию о каждом запросе и ответе.

Пример дерева результатов в интерфейсе jMeter
Пример дерева результатов в интерфейсе jMeter
Пример post_summary.txt
Пример post_summary.txt
Пример отчета report.json
Пример отчета report.json

Особенности работы генератора

Корректное заполнение Swagger важно для корректной работы автогенератора. И про это нельзя забывать.

Фильтры ONLY_METHOD, ONLY_PATH, ONLY_OPERATION работают по принципу UNION, то есть не исключают друг друга, а дополняют. Это для нас было неожиданно, но оказалось, что значения фильтров, которые мы передаем в Schemathesis, обрабатываются именно таким способом. Мы не стали спорить и просто подстроились под такую особенность.

Над политикой соответствия придется еще поработать и, вероятно, сделать более гибкую и сложную логику. Из-за особенностей работы Schemathesis возможно дублирование кейсов. Библиотека может сгенерировать кейс для проверки конкретной схемы на дубликаты или отсутствие какой-либо записи в сервисе, и он может не пройти установленную политику соответствия. Мы будем видеть два одинаковых набора данных, хотя сам Schemathesis проверял этим запросом разный функционал API.

Итоги и будущее автогенератора

Хотя кое-что в работе автогенератора еще не совершенно, стоит взглянуть на это:

Пример окончания работы автогенератора из BAT-файла.
Пример окончания работы автогенератора из BAT-файла.

Автогенератору потребовалось чуть больше 12 минут, чтобы сгенерировать и протестировать 65+ эндпоинтов, направив 2582 запроса. И это гораздо быстрее, чем неделя работы вручную! Цель «сократить время на работу с однотипными тестами» достигнута. Попадание же в остальные цели сможет подтвердить лишь время.

Что дальше? Я  сам активно использую автогенератор и считаю полезными такие потенциальные его доработки:

  • Встраивание инструмента в CI/CD. Отталкиваться будем от потребностей в конкретный момент.

  • Прикручивание БД для хранения тестовых данных.

  • Переход на полноценный тестовый фреймворк — например, Pytest. К тому же, сам Schemathesis хорошо подключается как библиотека.

  • Доработки по части отчетов. Использовать Allure-отчеты и интеграцию с TMS, в нашем случае test-ops.

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

После обратной связи от коллег из других команд, которым я презентовал инструмент на внутреннем митапе,  будут понятны дальнейшие приоритеты.

Мой первый блин вроде бы вышел не совсем комом и даже не пригорел. Продолжу работать над проектом — и в дальнейшем будет о чем еще рассказать.

Комментарии (1)


  1. kompilainenn2
    03.01.2026 17:08

    проект где-то доступен?