Меня зовут Артём Дружляков, я техлид QA в направлении кредитования. Сегодня я хочу рассказать о проекте, который мы реализовали в направлении тестирования микросервисов в Альфа-Банке, — о разделении автотестов по микросервисам.

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

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

Примечание. Этот материал дополняет статья «Как преобразовать огромный монорепозиторий с автотестами в микросервисы», которая рассказывает о той же проблеме, решенной несколько иначе.

О проблематике

Раньше мы все жили в одном большом проекте с нашими API-тестами. В какой-то момент зафиксировали, что тестировщикам стало слишком тяжело работать с проектом. Скорее всего проблема была в размере проекта — всё-таки 1,5 ГБ, даже локально его скачать с нуля занимает минут 10-20.

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

И так по кругу.

Та же проблема наблюдается в CI/CD, только время сборки там еще дольше — в среднем 20-30 минут. А сборка нам нужна для проверки целостности кода — проверяется компиляция, чекстайл. 

Следовательно, такое долгое время сборки увеличивает время проходения merge-request. Частая история, когда поправить надо одну строчку, из-за очередей на сборку в Jenkins ждать приходится час. Особенно такое поведение раздражало в случае минорного хотфикса для раскатки.

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

Сама архитектура проекта также вызывала сложности в поддержке и рефакторинге: много цепочек наследования, завязывание сотен классов на один базовый, завязывание всех http-классов для вызовов сервиса на один экземляр Retrofit, единый Spring-контекст на все тесты.

Все описанные проблемы приводили и к трудностям в погружению новичков. Нельзя методом тыка открыть любой тест и со 100% уверенностью сказать — здесь всё актуально.

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

Решение в разделении

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

Так на нашем проекте множество микросервисов (API) и много микросервисов дорабатываются несколькими командами, поэтому решили разделять автотесты по принципу «Один репозиторий содержит тесты на один API».

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

Итак, мы решили разделяться, а как быть и с чего начать? Для успешной реализации этого решения мы сформулировали ключевые требования:

  • Должна быть шаблонизация генерации репозитория со всеми базовыми классами.

  • Простота используемых технологий — должен быть простой стек.

  • Наличие CI/CD для каждого репозитория.

  • Возможность параллельного запуска тестов одновременно в разных репозиториях.

  • Общие библиотеки для инструментов и общих классов (для переиспользования).

Наметили план:


Давайте по нему и пойдём.

Подготовка внутренних библиотек

Сперва начали выносить в библиотеки базовую часть.

  • самые популярные константы, 

  • классы параметры для HTTP-запросов, 

  • общие POJO/DTO-классы, 

  • генерация и конфигурация тестовых пользователей,

  • и прочее.

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

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

Дополнительное преимущество проекта с библиотеками — покрытие юнит-тестами и запуск тестов в CI/CD. В старом проекте практически все самописные инструменты у нас не покрывались тестами и у нас не было гарантии, что при рефакторе мы не сломаем что-либо потребителям. Сейчас же прошедшие юнит-тесты являются обязательным артефактом для мержа в мастер и последующей релизной сборки.

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

Шаблонизация: репозитории по «кнопке»

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

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

В шаблоне организовали: 

  • структуру проектов (main часть с тестами),

  • самые базовые классы, 

  • настройки Gradle, 

  • gitmodule с пайплайном Jenkins,

  • настройки Allure Report.

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

Снимок экрана 2025-05-29 в 14.45.21.png

В результате у них получается вот такой небольшой проект.

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

Генерация по спецификации open-api

Поскольку мы начали наши микросервисы переводить на open-api, то не могли не воспользоваться и этим — подключили Open Source плагин автогенерации классов по спецификации.

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

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

Это значительно ускорило процесс написания тестов — сразу генерируются все классы с логикой http-запросов, POJO/DTO, заглушки для тестов, в которых тестовый метод генерируется с уже готовыми аннотациями, шаблонами проверок, где тестировщику нужно лишь заполнить конкретные значения, либо дописать логику. 

Ранее подключение плагина осуществлялось так (пример из документации):

openApiGenerate {
   dependsOn("downloadSpec")
   generatorName = "java"
   inputSpec = "$inputSpecPath" + "$inputSpecFileName"
   outputDir = "$rootDir/generated-sources/openapi"
   apiPackage = "ru.alfabank.mobile.api.realty.client.generated"
   modelPackage = "ru.alfabank.mobile.api.realty.client.generated.entity"
   id = 'api'
   templateDir = "$rootDir/src/main/resources/templates/"
   skipValidateSpec = true
   logToStderr = true
   enablePostProcessFile = false
   generateModelTests = true
   configOptions = [
           "dateLibrary": "java8",
           "serializationLibrary": "jackson"
   ]}

После выноса в отдельный плагин так:

classpath "ru.alfabank.alfa_mobile_qa_autotests_utils.plugin:qa-open-api-unirest-generation-plugin:${pluginVersion}"
apply plugin: 'qa-open-api-unirest-generation-plugin'
autotestsGenerationProperties {
specDevHubUrl = "http://artifactory/demo-api/0.0.1/demo-api-0.0.1-specs.zip!/demo-api.yaml"
}

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

Генерируются http-клиенты:

Снимок экрана 2025-10-13 в 14.46.23.png
Снимок экрана 2025-10-13 в 14.46.23.png

POJO/DTO-классы: 

Снимок экрана 2025-10-13 в 14.43.33.png
Снимок экрана 2025-10-13 в 14.43.33.png

И тесты:

Снимок экрана 2025-10-13 в 14.42.08.png
Снимок экрана 2025-10-13 в 14.42.08.png

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

Стандартизация проверок

Дальше мы стандартизировали написание проверок. Для этого я разработал библиотеку-обёртку над JUnit5, которая позволяет использовать цепочки проверок с понятными описаниями. Каждая проверка автоматически попадает как шаг в TMS. Как это выглядит:

@Test
@AllureId("474312")
@ApiMethod(CUSTOMERS_ME)
@DisplayName("Проверка вызова customers/me")
public void getCustomersInfoTest() {
   var response = userInfoApi
           .executeCustomersMeRequest(requestHeaders)
           .isStatusCodeEquals(200);
   expect(response.getBody(), isNotNull(), "Проверка, что тело ответа не null");
   var responseBody = response.getBody();
           initChecksChain().softChecks()
           .expect(responseBody.firstName(), isEquals(user.getName()),
                   "Проверка, что имя пользователя в ответе API соответствует значению в БД")
           .expect(responseBody.lastName(), isEquals(user.getLastName()),
                   "Проверка, что фамилия пользователя в ответе API соответствует значению в БД")
           .expect(responseBody.mobilePhone(), isEquals(user.getPhoneNumber()),
                   "Проверка, что номер пользователя в ответе API соответствует значению в БД")
           .execute();
}

А такой результат получаем:


В итоге в результате прохождения автотеста мы получаем и читаемый результат запуска и читаемый тест кейс (стереотипного кода меньше). 

Переиспользование кода автотестов 

Для нас было важно дать возможность переиспользовать код между проектами, чтобы не описывать несколько раз одно и то же, потому что часто в тестах на API «А» нужно использовать методы из API «Б».

Мы настроили возможность публикации артефактов main-части репозиторием автотестов в хранилище, что позволило использовать клиенты и вспомогательные классы из одного проекта в другом. Например, автотесты на API «A» могут использовать http-клиенты и инструменты из проекта API «Б», не дублируя логику.

В результате помимо репозитория с автотестами мы получаем ещё и уже скомпилированный jar-артефакт, который мы можем переиспользовать в проектах API и UI тестов. Пример: через implementation подключается такая библиотека и используются в тестах, если нужно что-то вызвать.

Настройка CI/CD

CI/CD мы построили на основе единого пайплайна, подключаемого через gitmodules. Каждый репозиторий имеет свои этапы сборки и запуска, а также возможность запуска тестов. Такой подход дал возможность запустить автотесты на конкретную API из CI/CD по кнопке, попутно это дало удобство разработчикам.

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

Здесь всё просто. Выбираем TEST_RUN, запускаем тест. 

Снимок экрана 2025-10-27 в 22.08.24.png

Выбираем билд — сборка. Всё это запускается автоматически через хуки при пушах в репозитории.

Есть ещё кое-что про пайплайн. Мы организовали пайплайн, которым можно запустить тесты сразу на несколько репозиториев.

Снимок экрана 2025-10-27 в 22.11.12.png

При этом можно выбирать как отдельно по репозиторию, так и выбрать команду, и запустятся все репозитории, в которых команда пишет тесты (это всё указывается в настроечном файлике). Если нужно запустить тесты с определенным признаком, например, Smoke — это тоже работает.

В таком формате запуски идут в параллельном режиме. Джоба триггерит маленькие джобы проектов в параллельном режиме. 

Интеграция с Test Ops 

Все проекты интегрированы с Allure Test Ops. Результаты тестов публикуются централизованно в один общий проект.

Общая джоба запуска умеет собирать результаты в один отчет, что упрощает разбор при запуске автотестов на несколько микросервисов. Также и в обратную сторону — мы можем запускать автотесты в наших проектах через UI-интерфейс Test Ops.

Результаты

  • Время локальной сборки сократилось с 1-4 минут до 5 секунд (максимум). В зависимости от локального железа прирост в скорости от 25 до 100 раз.

  • Сборка в CI — с 20–30 минут и выше до 90 секунд. В среднем прирост скорости в 30 раз. Также мы самим архитектурным подходом устранили очереди в Jenkins.

  • Повысилась устойчивость к поломкам, потому что для любого изменения нужно пройти пулл-реквест, собрать билд, чек-стайлы, SonarQube и т.д. 

  • Чтобы внести изменения в библиотеке нужно, чтобы собрался зеленый билд, чтобы прошли юнит-тест. Проект с библиотеками также покрывается документацией (и тестами), что упрощает работу с ними.

  • Повысилась скорость доработок автотестов. Нам не нужно ждать по полчаса пока соберется каждый билд: минута, билд зеленый, если к коду нет замечаний — мерж  в мастер.

  • Появилась автогенерация.

  • Упростили стек и саму архитектуру, репозитории простые, прозрачные, устранены сложные цепочки наследований.

  • Нам стало проще внедрять изменения — новые подходы и практики мы можем подключать в репозитории постепенно. Например, при попытке подключить SonarCube в старый проект я насчитал несколько тысяч ошибок — править их было бы сложно. Здесь же мы можем постепенно раскатывать изменения, не влияя на соседние репозитории.

  • Появилась чёткая ответственность за каждый репозиторий.

  • Упростилась навигация и погружение новых сотрудников.

  • Разработчикам стало проще жить. Разработчик в может взять джобу, запустить в ней тесты на своей фича ветке (изолированный образ в Kubernetes), если автотесты все прошли или хотя бы smoke-тесты прошли, , то разработчик может передать задачу в тестирование.

Конечно, у подхода есть и сложности. Теперь нужно клонировать несколько репозиториев (справедливости ради, их у нас и так не мало). Нужно поддерживать общие библиотеки и пайплайны. Настроить запуск тестов по всем API стал несколько сложнее,, но на практике он требуется редко — чаще нужны изолированные запуски.

Выводы

  • Такой подход эффективен, если у вас множество микросервисов и несколько команд работают над разными API.

  • Критически важно иметь шаблонизацию создания репозиториев.

  • При наличии OpenAPI — обязательно настройте автогенерацию. Так же  Open Api может дать и дополнительные возможности - например, смотреть если эндпоинты из спецификации вообще в автотестах.

  • Необходим доступ к хранилищу артефактов для публикации и переиспользования библиотек.

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


Телеграм-канал Alfa Digital, где рассказывают о работе в IT и Digital: новости, события, вакансии, полезные советы и мемы.

Как преобразовать огромный монорепозиторий с автотестами в микросервисы
Здравствуйте! Меня зовут Владислав Донченко, я ведущий специалист по тестированию в Альфе. Хочу поде...
habr.com
Архетипы дизайн-лидов
Привет! Я — проектный менеджер в дизайн-команде Альфа-Бизнес, и в основном работаю с дизайн-лидами. ...
habr.com
Что не так с отчётом MIT NANDA, в котором говорят, что 95 % AI-проектов проваливаются?
Если кратко — он убогий. Я не фанат «ИИ», а особенно бездумного хайпа с громогласными заголовками. Н...
habr.com
Cчитаем финансовый эффект от ИИ, ML и LLM в банке без магии — с калькулятором
Утром деньги, вечером ИИ-проекты. Пока бедолаги из отчета NANDA жалуются, что 95% их ИИ-проектов не ...
habr.com

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


  1. gigimon
    23.12.2025 20:46

    Просто к тестам надо относиться также, как и коду. Если у вас независимые микросервисы, используйте прямо в этом микросервисе независимые от других репо тесты. Если у вас микросервисы используют "общие" библиотеки, используйте в тестах такой же подход.

    Не совсем понимаю проблему компиляции (длительности), когда тесты собраны в один репозиторий. Мы выносили просто в отдельную сборку и отдельный docker контейнер. В пайплайне сервиса просто запускали докер контейнер и запускали нужные для этого репозитория тесты. И ровно также, как и проект делали тэги тестов, т.к. репозиторий с тестами ровно такой же компонент системы (один из микросервисов), который также версионируется, релизится и т.п.


    1. art101994 Автор
      23.12.2025 20:46

      Проблема компиляции растет из-того, что в проекте работает огромное количество команд, а пайплайн настроен так, что на каждую сборку репо качается с нуля.
      Это тянет огромные очереди.
      Докер-контейнер, увы, никто не подключил)

      Касательно микросервисов и организации тестов на них - полностью согласен, мы так и поступили.