Статьи

Как мы в Veeroute убили ручной запуск автотестов и перешли на интеграционный абсолют

Еще несколько лет назад типичный день релиза в Veeroute напоминал групповую медитацию на выносливость. Несколько человек вручную прогоняли бесконечные сценарии. Кто-то планомерно сводил результаты в огромную, не всегда надёжную Excel-таблицу. А перед самой «выкаткой в прод» команда, дружно скрестив пальцы, надеялась на успех. Подобные ситуации знакомы разработчикам не понаслышке.

Сегодня мы «выкатываем» релизы каждую неделю, а промежуточные сборки — по несколько раз в день. У нас крутятся 6 500 базовых автотестов и около 4 000 жестких стресс-сценариев. При этом ручных прогонов нет вообще. От слова «совсем». Время от идеи фичи до её выкатки в продакшн с полным циклом проверок сократилось всего до 2 дней.

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

Почему «ручками» больше не работало

Veeroute — это не просто CRUD-приложение, а сложный логистический продукт со множеством интеграций. За каждым запросом к нашей системе стоит не банальное «отправил — получил», а зубодробительная цепочка условий: временные окна, ограничения по габаритам, специфические форматы данных и куча зависимостей.

Пока сценариев было пара десятков, ручное тестирование еще как-то справлялось. Но когда счет пошел на сотни, процесс превратился в черную дыру. Ручной прогон превращался в ад: команда тратила 1–2 дня, проверяла тонну всего, но на выходе получала мизерный объем покрытия и нулевую уверенность. Мы поняли: либо мы строим автоматизированную систему, либо погрязнем в бесконечном «героизме» и ночных «дебагах».

Шаг 1. Документация как живой контракт

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

  • — синтетика: гоняем внутреннюю логику и безумные комбинации данных;
  • — клиентские сценарии: в точности повторяем жизнь реального пользователя.

Внутри каждой группы прописали позитивные и негативные кейсы. Это помогло нам перестать хаотично проверять «всё подряд» и сфокусироваться на факторах, которые реально могут сломать продукт.

От smoke-тестов до fuzzy-безумия

Чтобы упорядочить этот хаос, мы разложили тесты по понятным полочкам:

  • — smoke: быстрая проверка системы;
  • — базовые: железные сценарии, которые обязаны работать;
  • — стресс-тесты: проверяем, как система держит удар под высокой нагрузкой;
  • — fuzzy-тестирование: наш любимый режим, когда мы намеренно скармливаем системе абсолютно дикие, зашумленные данные, чтобы найти неочевидные баги.

Нам мало, чтобы работал стандартный «хэппи-пас». Контроль ошибок — вот где скрыта самая соль. Нам важно знать, как поведёт себя система на экстремальных границах. Поэтому с помощью гибких типов фреймворка Karate мы настроили не просто сверку структуры, а тотальный контроль: проверяем диапазоны, типы данных и регулярные выражения (regexp). Каждый ответ сверяется с контрактом из документации до последнего знака.

Наш стек: Франкенштейн из Karate + Python

Мы долго выбирали инструменты и в итоге собрали связку из Karate и Python. Подход у нас гибридный: мы используем Karate как standalone JAR, а Python вызываем прямо внутри сценариев через CLI, пробрасывая в него нужные данные.

Почему так? Потому что наша команда отлично знает Python. Нам показалось намного проще и быстрее использовать силу Python-скриптов через консоль, чем разбираться в тонких материях и капризах работы JavaScript внутри Karate. Мы встраиваем утилитарный Python-скрипт туда, где нужна сложная математика или подготовка данных, а результат его работы верифицируем в том же сценарии Karate. Получается идеальный симбиоз: гибкость сценариев одного инструмента и безграничная мощь другого.

Главная суперсила Karate — это невероятно гибкий механизм проверки типов и контрактов ответа. Мы заставляем фреймворк проверять ответы до последнего знака. Вот реальный пример того, как выглядит проверка структуры и валидация токена:

* def struct_token =
"""
{
  sub: '#? typeof _ === "string" && _.length >= 2 && _.length <= 256 && /[a-zA-Z0-9_]+/.test(_)',
  exp: '#number? _ > 0',
  iat: '#number? _ > 0',
  company: '#? typeof _ === "string" && _.length >= 3 && _.length <= 256 && /[a-zA-Z0-9_]+/.test(_)'
}
"""
# А теперь просто и элегантно сверяем ответ бэкенда с нашей матрицей:
* match response == struct_token

Генератор данных и Fuzzy: как решить проблему Оракула

Наш генератор синтетических данных мы написали сами ещё до того, как из каждого утюга начали кричать про LLM. Он работает по fuzzy-концепции, создавая терабайты разнородных данных на основе файлов конфигурации. У нас в обойме более 500 уникальных датасетов.

У любого инженера при слове «фаззинг» возникнет резонный вопрос: «Если генератор швыряет в систему случайный шум, то как автотест понимает, где правильный ответ, а где баг?» (так называемая проблема Оракула).

Мы решили это разделением логики. Наш «фаззер» работает строго в двух режимах:

  • — режим «Негатив»: генератор намеренно портит данные, выходя за рамки контракта (шлет слишком длинные строки, не тот тип данных). Мы заранее знаем, что система должна вернуть 400 Bad Request;
  • — режим «Позитив»: генератор создает жесткие, нагруженные, но валидные данные, которые укладываются в диапазоны нашей документации. Здесь система должна выдать 200.

А что насчет 500 Internal Server Error? В нашей парадигме пятисотая ошибка — это абсолютный успех самого тестирования. Система упала, баг пойман. Пока разработчики не поправят пятисотку, никакого релиза не будет. Более того, если система вернула 400, то мы не останавливаемся на проверке кода ответа — мы сверяем весь контракт: должно быть сообщение, должен быть тип, к которому относится 400 статус, и пр.

Что ловит fuzzy: две истории

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

История первая «Маршрут-Годзилла». Один из случайно сгенерированных стресс-датасетов собрал такой гигантский размер логистического маршрута, что он физически превысил лимиты нашей базы данных. Система просто не смогла его сохранить и упала. Ни один живой тестировщик в здравом уме не додумался бы вручную составить такой объем данных. А генератор смог.

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

Поучительная драма про суффикс «-quickfix»

Наша дотошность в проверке версий — это не синдром отличника, а шрамы после реальных тупиков.

Когда мы только раскачивали автоматизацию, один из крупных клиентов во время интеграции решил подстраховаться: перед каждым действием его система дергала наш «роут» версии приложения. Мы настроили тест клиента строго по документации, где было четко прописано: блок «версия» состоит только из целых чисел (integer).

Но в один прекрасный день наши разработчики выкатили срочную правку и приписали к версии строковый суффикс «-quickfix». На уровне интеграции у клиента сорвало крышу, типы данных не совпали, и поведение системы стало непредсказуемым.

Урок был усвоен. Теперь у нас проверяется абсолютно каждый «эндпоинт», включая диапазоны, регулярки и даже банальный запрос версии. Больше никаких сюрпризов.

Скорость против прожорливости (наши метрики)

Давайте перейдем к цифрам, которые радуют бизнес и пугают сисадминов.

  • — Скорость «пайплайна»: раньше ручной прогон занимал до двух дней. Сегодня наш огромный «смоук-пакет» из более чем 4 500 тестов пролетает всего за 1 минуту! Полный набор интеграционных тестов отрабатывает за 20 минут. Стресс-сценарии занимают больше времени, но там и цели другие;
  • — Прожорливость Karate: за феноменальную скорость и гибкость приходится платить железом. Karate под капотом работает на Java и невероятно требователен к памяти. В пике на тяжелых стресс-тестах наш фреймворк может «проголодаться» и откусить до 60 ГБ оперативной памяти. С этим аппетитом приходится постоянно воевать, оптимизировать скрипты и выделять под тестовые контуры мощные сервера.

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

Среда разработки  →  Среда раннего доступа  →  Продакшн

Пока все тесты не загорятся девственно-зеленым цветом на «деве», катить код дальше — табу. Smoke и базовые тесты запускаются автоматически при любом «коммите», а тяжелую артиллерию (стрессы) мы натравливаем каждую ночь.

Все результаты мы открыто обсуждаем на общих митингах разработки и QA. Это не унылая отчетность, а живой ритуал: если что-то «покраснело» — мы бросаем силы на разбор полетов здесь и сейчас.

Что нам это дало (кроме крепкого сна)

  • — Excel для нас официально мертв. Никаких ручных проверок и табличек;
  • — релизы без валидола. Мы спокойно катимся раз в неделю, а промежуточные обновления выпускаем по несколько раз в неделю;
  • — чистый «прод». Количество инцидентов у клиентов упало до единичных случаев. Баг на «проде» теперь — это ЧП и событие года, а не серые будни.

Крутая фишка: если у клиента все-таки случается какой-то уникальный инцидент, мы не просто чиним его. Мы берём его обезличенные данные, упаковываем в автотест и добавляем в общую базу навсегда. Это дает 100% гарантию, что данный конкретный баг больше никогда не вернется в код.

Вместо вывода

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

Если вам интересна тема внедрения автоматической маршрутизации и сама технология, позволяющая оптимизировать не только логистику, но и другие сферы бизнеса, то материалы в нашем блоге — для вас!