Привет, Хабр. Меня зовут Сергей Вертепов, я senior backend инженер. Это небольшая обзорная статья отом, как мы тестировали монолитное приложение Авито, и что изменилось спереходом намикросервисную архитектуру.
Тестирование в домикросервисную эпоху
Изначально приложение Авито было монолитным. Справедливости ради, монолит унас и сейчас довольно большой, но микросервисов становится всё больше и больше.
Выше схема того, как выглядит монолитное приложение. Унас есть пользователь, есть уровень представления, бизнес-уровень, уровень данных и база данных, изкоторой мы их получаем. Когда унас был большой-большой монолит, то объектом тестирования было приложение сбэкендом наPHP, фронтендом наTwig, и совсем немножко наReact.
Классическая пирамида тестирования длямонолитного приложения выглядит так:
- Много юнит-тестов.
- Чуть меньше интеграционных тестов.
- Ещё меньше сквозных тестов.
- Поверх всего непонятное количество мануальных тестов.
Расскажу, как мы пытались прийти ктакой пирамиде, и что унас было изинфраструктуры.
У нас был собственный тестовый framework наPHP сPHPUnit подкапотом. Система генерации тестовых данных унас тоже своя. Это ресурс-менеджер, который позволяет создать любой необходимый запрашиваемый ресурс длятестирования. Например, пользователя сденьгами, собъявлениями вопределённой категории наАвито, пользователя сопределённым статусом.
Система просмотра отчётов тоже самописная. Кроме неё мы написали собственный jsonwire-grid. Grid это система оркестрации селениумными нодами, она позволяет потребованию поднять селениумную ноду. Узнать подробности проGrid, как мы его разрабатывали и какие технологии использовали, можно издоклада моего коллеги Михаила Подцерковского cHeisenbug 2018года.
Также унас есть собственный selenium-maper. Мы написали собственную библиотечку, которая позволяет выполнять запросы поjsonwire-протоколу.
Наш CI-pipeline выглядел следующим образом: происходил какой-то CI Event, пусть для примера это будет пуш врепозиторий. ВCI собирался артефакт длязапуска тестов. Самописная система параллельного запуска тестов парсила артефакт и начинала запускать тесты накуче разных нод.
В качестве тестового приложения мы использовали РНР-имплементацию Selenide, полный порт сJava. Но впроцессе мы отнего отказались, потому что Selenide было тяжело поддерживать, сам он уже не развивался. Мы написали свой, более легковесный, PowerUI, вокруг которого построили и архитектуру тестовых приложений скастомными матчерами, селекторами и так далее. Этот продукт сильно упростил длянас тестирование и построение отчётов. Соответственно, дальше PowerUI черезjsonwire-grid входил вселениумную ноду и выполнял необходимые действия.
Само тестовое приложение унас дополнительно ходило вресурс-менеджер длягенерации тестовых данных, и потом уже отправляло данные внашу систему просмотра отчётов Test Report System.
В целом, втакой парадигме мы прекрасно жили. Вначале релизы большого монолитного PHP-приложения были раз вдень, потом их количество выросло дотрёх, а впоследствии и вовсе дошести. Унас было несколько тысяч Е2Е-тестов сбольшим покрытием, и они были довольно легковесными. Всреднем они выполнялись порядка минуты, заредким исключением. Тест, который проверял огромный кусок бизнес-логики мог занимать две-три минуты. Унас был минимум ручного регресса и минимум багов впродакшене.
Тестирование в микросервисной архитектуре
Со временем мы стали переходить намикросервисную архитектуру. Основные её плюсы это масштабирование, быстрота доставки фич и отказоустойчивость.
С монолитом пирамида тестирования унас не получилась. Вместо неё была мороженка тестирования. Счем это было связано? Е2Е-тесты благодаря разработанной инфраструктуре были довольно быстры и не причиняли особой боли. Поэтому мы делали основной упор наних. Мы даже могли пренебрегать юнит-тестами.
Сприходом микросервисной архитектуры такой подход перестал работать. Огромная часть бизнес-логики уехала вотдельные сервисы, их становилось всё больше. На 2020год унас порядка 2,5 тысяч разных репозиториев. Втаком случае, когда мы запускали Е2Е-тесты какой-то сервис мог, например, резко перестать отвечать. Если он отвалился, все тесты, которые ходили вэтот сервис и были блокирующими длямержа, тоже начинали падать. Соответственно, унас просто падал time tomarket, так как люди не могли мержиться из-западающих тестов. Мы были вынуждены сидеть и ждать, пока придёт оунер конкретного сервиса, разберётся, что происходит, перевыкатит его или разберется спроблемами.
После этого мы внедрили карму тестов. Это очень простой механизм. Он работает наоснове самописной системы отчётов, которая имеет всю необходимую историчность данных. Карма проверяет, что тест упал, и дальше идёт смотреть, встречается ли подобная ошибка призапуске тестов вдругих ветках. Ошибка это хэш трейса. Мы берём полный трейс, хэшируем его и сохраняем. Если мы видим, что ошибка стаким хэшем встречается ещё натрёх ветках, мы понимаем, что проблема не вветке, накоторой запущены тесты, а что она носит общий характер. Если ошибка общая и не имеет отношения кконкретной ветке, то мы позволяем вмержить эту ветку.
Да, таким образом мы маскируем проблемы, но, тем не менее, это решение сильно упростило жизнь разработчиков. Вслучае, если разработчик пропустил какой-то баг, унас остаётся процесс деплоя. Вдеплое никакая карма уже не работает, всё наручном апруве тестировщиков и разработчиков, то есть мы прямо смотрим, что и как унас происходило. Если находим проблемы, выкатка блокируется дотех пор, пока проблемы не решат и не сделают хотфикс.
Количество микросервисов растёт, а количество тестировщиков не очень. Как правило, унас наюнит один ручной тестировщик. Понятное дело, что водиночку довольно-таки тяжело полностью покрывать нужды всех разработчиков изкоманды. Всреднем это несколько фронтендеров, несколько бэкендеров, ещё автоматизаторы и так далее.
Чтобы решить эту проблему, мы стали внедрять методологию Agile Testing. Суть этой методологии состоит втом, что мы предотвращаем баги, а не ищем их. Тестирование мы обсуждаем наProduct Backlog Refinements. Мы сразу определяем, как будем тестировать какую-то фичу: достаточно ли покрыть её юнит-тестом и если юнит-теста достаточно, какой это должен быть юнит-тест. Обсуждение происходит вместе стестировщиком, который определяет, нужно ли будет дополнительно провести ручное тестирование. Как правило, юнит-теста бывает достаточно. Плюс тестировщик может подсказать ещё какие-то кейсы, а также может предоставить чеклист, наоснове которого мы напишем нужные юнит- или функциональные тесты.
Разработка унас идёт отприёмочных тестов, то есть мы всегда определяем тесты, которые будут приняты приразработке. Подробнее пропереход на Agile Testing уже рассказывала моя коллега Алёна изсоседнего юнита. Встатье она пишет овнедрении методологии напримере своей команды, но это справедливо длявсего Авито.
Но Agile Testing невозможен безShift-left тестов. Shift-left testing это методология, прикоторой мы тестируем каждый деплой и прикаждом пуше прогоняем все необходимые тесты. Выкатка без этого невозможна. Но тесты приэтом должны быть легковесными. Основная суть подхода находить дефекты наранних этапах, чтобы разработчик мог запустить необходимый тест влюбой момент, когда пишет код. Также он обеспечивает непрерывное тестирование вCI, и возможность автоматизации чего угодно.
Во время Shift-left тестов мы разбиваем большие, тяжёлые тесты накучу маленьких, чтобы они запускались и выполнялись быстрее. Мы декомпозировали наши огромные Е2Е-тесты накомпонентно-интерфейсные тесты, наюнит-, интеграционные тесты, функциональные тесты, то есть начали распределять Е2Е попирамиде. Раньше запуск тестов мог занять 2030минут и требовал танцев сбубном. Сейчас влюбом микросервисе тесты прогоняются за5-10минут, и разработчик сразу знает, сломал он что-либо или нет.
Также мы внедрили методологию контрактных тестов. Сокращение CDC-тесты значит Consumer-Driven Contract тесты. Когда мы пушим изменение кода врепозитории, специально написанный брокер собирает всю необходимую информацию потому, какие написанные CDC-тесты есть, и дальше понимает, ккакому сервису они имеют отношение.
Сами CDC-тесты пишутся настороне консьюмера сервиса. Привыкатке продюсера мы прогоняем все написанные тесты, то есть проверяем, что продюсер никак не нарушает контракт. Подробнее проэто рассказывал всвоём докладе Фрол Крючков, который как раз драйвил эту идею.
Помогли ли нам CDC-тесты? Нет, потому что появилась проблема стем, что сами консьюмеры не поддерживают свои тесты. Как результат тесты нестабильны, из-заэтого получалось, что наши продюсеры не могли вовремя выкатиться. Приходилось идти и фиксить тесты состороны консьюмеров. Плюс тесты все писали по-разному, наодну ручку мог быть десяток различных тестов, которые проверяют одно и то же. Это неудобно и долго. Поэтому отидеи CDC-тестов мы отказались.
Недавно мы внедрили PaaS. Наша архитектурная команда разработала очень удобный инструмент, благодаря которому можно быстро развернуть набойлерплейтах сервис и сразу начать его разрабатывать. Приэтом не надо думать ни обазах, ни омиграторах и прочих инфраструктурных штуках. Можно сразу начать писать код и катить миграции.
Теперь сервис-потребитель и сервис-продюсер общаются между собой через Api Gateway. Настороне Api Gateway есть валидация контрактов наоснове бриф-файлов. Бриф-файл это очень простой структурный файлик, который описывает сервисное взаимодействие. Есть бриф-файл настороне продюсера, он описывает то, как мы должны общаться сэтим сервисом. Консьюмер копипастой берёт необходимую себе ручку, необходимую структуру, затаскивает всё это всвой сервис, и генерирует наоснове этого клиенты.
Соответственно, унас идёт валидация бриф-файлов, что унас всё впорядке, что мы не ломаем никакое сетевое взаимодействие. Сейчас уже такая валидация даже будет блокировать мерж, если унас где-то нарушаются контракты. Мы как раз проверяем контракты настороне инфраструктурных штук.
Ещё мы внедрили такую штуку, как service mesh. Service mesh это когда рядом скодом сервиса поднимается sidecar, который проксирует все необходимые запросы. Запрос идет не всам сервис, его сначала получает sidecar, который прокидывает необходимые заголовки, проверяет, роутит маршруты, и передаёт запрос сервису. Cервис передаёт запрос обратно вsidecar, и так дальше поцепочке.
Про service mesh подробно можно узнать издоклада Саши Лукьянченко сDevOpsConf 2019года. Внём Саша рассказывает прото, как разрабатывал решение и как мы кнему пришли.
На основе sidecar мы внедрили OpenTracing. Это технология, которая позволяет полностью отследить запросы отсамого фронта доконечного сервиса и посмотреть, какие были отвалы, сколько шёл запрос.
Это интерфейс Jaeger UI. На скриншоте трейсинг запросов
современем выполнения и маршрутом
Также с помощью service mesh мы сделали Graceful Degradation тестирование. Graceful Degradation тестирование это когда мы отключаем какой-то сервис и проверяем, как унас будет работать приложение. Мы смотрим, выдаст ли приложение какую-то ошибку или полностью крашнется. Такого развития событий мы не хотим, но так как микросервисов становится много, количество точек отказа тоже растёт. Такой вид тестирования позволяет проверить поведение всей системы приотказе одного изсервисов.
На скриншоте боевой пример содного изнаших тестов вовремя прохода. Мы отключали сервис услуг и проверяли, что выдадим пользователю читаемое сообщение.
Всё это работает благодаря service mesh утилите Netramesh. Достаточно прописать заголовок X-Route, наш sidecar перехватывает запрос досервиса и перенаправляет куда надо. Вконкретном случаем мы его перенаправляли вникуда, будто бы сервис отвалился. Мы могли сказать ему, чтобы он вернул пятисотую ошибку, либо могли сделать таймаут. Netramesh всё это умеет, единственная проблема, что здесь необходимо черезDevTools-протокол добавлять ковсем запросам необходимый заголовок.
В сухом остатке
Сейчас для тестирования в микросервисной архитектуре мы используем:
- Карму для E2E-тестов.
- Методологию Agile Testing.
- PaaS c Api Gateway.
- Service mesh, благодаря которому работают OpenTracing и Graceful Degradation тестирование.