Меня зовут Павел Коростин, я техлид тестирования мобильного банка для юридических лиц в Альфа-Банке. Бэкенд мобильного банка — это около 300 микросервисов, работающих в k8s, развитием которых занимаются примерно 50 команд, разделённых на функциональные области. За каждой командой закреплены сервисы, которые они поддерживают, разрабатывают, тестируют и, соответственно, пишут автотесты. Раньше все тесты на сервисы «жили» в одном монорепози��ории, но в определённый момент мы поняли, что дальше так работать уже не можем и нужно как-то разъезжаться.

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

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

№1. Как преобразовать огромный монорепозиторий с автотестами в микросервисы.

№2. Как кратно ускорить написание и запуск автотестов и избавиться от громоздкого монолита.

Как будем делить?

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

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

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

Плюсы такого решения, по сравнению с дроблением на более мелкие репозитории:

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

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

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

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

План разъезда по шагам

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

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

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

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

Теперь к реализации плана.

№1. Общий код

Создание core-библиотеки

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

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

Унификация правил написания кода

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

Процесс написания тестов сводится к трём основным шагам.

№1. Добавляем в зависимости нашу библиотеку. 

№2. Добавляем веб-клиент для нашей API. Мы используем OpenAPI спецификации для описания интерфейса и у нас настроена автогенерация кода клиента. Это оказалось нам на руку — ничего не надо придумывать: у тестировщиков уже есть готовый клиент для взаимодействия с API.

dependecies {
    implementation(“ru.alfabank:qa-tests-core:1.1.0”)
	implementation(“ru.alfabank.openapi.specs:api-webclient:0.125.2”)
}

3. Пишем тест с помощью методов из core-библиотеки.

public class LogopassesApiTest extends BaseTest {
  
    @Inject
    @RestEndpoint
    private LogopApi logopApi;
  
    @Test
    @ApiMethod(“GET /logop”)
    void getLoginsTest() {
        var response = logopApi.getLogopWithHttpInfo( id: 1234567890);
        var body = assertResponseWithBody(response);
        assertThatBodyHasPropertyWithValue(body, “[0].lgn”, “login-123”);
        assertThatBodyHasPropertyWithValue(body, “[2].psd”, “pass-123”);
    }
}

Объясню, что находится в коде выше:

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

  • Далее инжектится клиент (LogopApi) — это автосгенерированный клиент на базе OpenAPI-спецификации. Все конфиги DI тоже в core-библиотеке.

  • В конце непосредственно сам тест, в котором есть вызов и проверка ответа от сервера.

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

Унификация отчета

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

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

Мы не пишем тест-кейсы для API, которые потом автоматизируем, а придерживаемся подхода code-first — сразу пишем код и запускаем. А уже в TestOps отчёты переходят в тест-кейсы. Так у нас пополняется наша тестовая модель. А тестовая модель должна быть, мы же банк. Дополнительно есть возможность пройти тесты руками на случай непредвиденных обстоятельств.

Теперь о том, как начать пользоваться новым инструментом.

№2. Создание проекта

Решили максимально всё автоматизировать и написали скрипт, который сам создаёт репозиторий и джобу для запуска тестов на нашем CI-сервере. 

Для запуска скрипта используем обычную Jenkins-джобу. Для старта QA-инженер просто выбирает тип репозитория (для API-тестов или UI), прописывает название своей команды — и всё — настроек минимум.

После запуска создаётся git-репозиторий на нашем сервере и пушит в него минимальный Gradle-проект, который можно склонировать локально и начать писать тесты. 

И вторым шагом скрипт создает джобу для запуска автотестов на нашем CI. Так как мы используем Jenkins, то для описания пайплайна запуска тестов используем Jenkins-файл (это файл, в котором в декларативном стиле описывается, что джоба должна делать). 

Чтобы не копировать файлы из одного репозитория в другой, мы поместили их в одно место и подключили во все репозитории с тестами как git submodule. Это удобно: для раскатки изменений в нашем пайплайне запуска тестов мы просто меняем в одном месте и при запуске тестов подтягивается актуальная версия.

Так мы свели создание проекта к нажатию «одной кнопки»: автоматом создаётся личный репозиторий с минимальным рабочим примером автотеста и джоба для запуска в Jenkins с настроенной интеграцией с TestOps.

№3. Код-ревью

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

Что мы сделали, чтобы уберечь лидов от тревог?

1. Всё, что можно автоматизированно проверять, перенесли на утилиту Checkstyle. Это неплохой статический анализатор, который используется в Java-проектах. Имеет много настроек: можно настраивать отступы, формат названия сущностей в коде, длину строк и многое другое. Настроили конфиг, раскатили на всех, и при создании pull request в командных репозиториях прогоняется билд с Checkstyle. Если что-то не так, то Checkstyle подсветит.

2. Запретили пушить в мастер напрямую. Для внесения новых изменений нужно создать pull request, чтобы прогнался билд.

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

Дополнительно в наш корпоративный мессенджер пушится сообщение о том, что открылся новый pull request, и тегаются ответственные ревьюеры.

Как стимулировать команды на переезд?

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

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

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

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

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

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

  • И много всего другого:)

Общая мысль такова — то, от чего тестировщики страдали в большом репозитории, мы постарались исправить в новом (и сделали упор в нашей «маркетинговой кампании»).

Краткие итоги

№1. Мы ушли от основной проблемы с нашим монорепозиторием. Ура! 

№2. Несмотря на разъезд, удалось сохранить контроль над нашим ядром, инициативной группой тестеров, получилось сохранить развитие и контроль.

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

№4. Пришли к структуре, масштабируемой на любое количество команд. С монорепозиторием было круто работать, когда команд от 5 до 10, а дальше начались проблемы. А решение, которому посвящена статья, можно применить на любое количество команд в будущем.


Подписывайтесь на Телеграм-канал Alfa Digital — там мы постим новости, опросы, видео с митапов, краткие выжимки из статей, иногда шутим.

Как нарезать Android-монолит с помощью compile-time плагинов?
Привет, на связи Федотов Михаил и Абдульманов Эдуард , мы технические лидеры Android-разработки в Ал...
habr.com
Что не учитывают паттерны и почему мы не получаем то, что ожидаем?
Мы часто проектируем «правильно»: используем проверенные подходы, следуем best practices, применяем ...
habr.com
Зачем считать вклад в продукт и как им управлять
В 2022 году мы приняли решение объединить множество показателей, связанных с разработкой, в интеграл...
habr.com
Из дирижера в зрители: как проджекту научить свою команду самостоятельности, чтобы она в нем больше не нуждалась
Крошка Макс ко мне пришел, И спросила кроха: «Если все решаю сам — Это, значит, плохо?» Я в ответ: «...
habr.com