Не так давно мне довелось поучавствовать в конференции, на которой один из докладов был посвящен автоматизации тестирования с применением практик новомодной микросервисной архитектуры.
На одном из слайдов этого доклада из минусов микросервисной архитектуры указывалась сложность тестирования. В большинстве источников тестирование микросервисных приложений практически не упоминается, поэтому возникло желание по возможности разобраться в возможностях тесторавания микросервисной архитектуры (MSA), понять, что надо учитывать на этапе дизайна такого приложения, и как максимально облегчить жизнь себе и ближнему своему.
Микросервисы. Начало.
Покопавшись в интернете я нашел кучу информации о MSA (MicroService Architecture) и его старшем брате SOA (Service Oriented Architecture), в том числе и на Хабре, поэтому не буду останавливаться подробно на том, что это такое. Вот кратко основные принципы MSA:
- Модульность. Приложение состоит из нескольких сервисов, представляющих собой законченные приложения
- Независимость реализации. Сервисы могут быть реализованы на различных языках программирования, с использованием различных технологий
Отсюда ряд плюсов:
- Высокая стабильность. При отказе одного из сервисов, например, из-за программной ошибки, есть возможность откатить неработающий сервис или установить новую, более стабильную версию, не перезапуская всего приложения.
- Разнообразие технологий. Сервисы не ограничены только одной технологией, принятой для всего приложения.
- Независимое развертывание. Простые сервисы проще разворачивать, и меньше вероятность отказа системы.
И недостатков:
- Сложность разработки из-за наличия различных технологий.
- Время работы напрямую зависит от того, как сервисы общаются между собой.
Теперь давайте попробуем разобраться, как лучше использовать возможности микросервисной архитектуры для эффективного тестирования.
«А что нам надо?»
При проектировании микросервисного приложения на передний край выходит проблема взаимодействия микросервисов между собой. Т.к. каждый сервис может быть написан на своем языке программирования и использовать различные технологии, то возникает необходимость в разработке дополнительного модуля, отвечающего за коммуникацию между сервисами.
Если приложение относительно небольшое, то можно обойтись поддержкой REST-заросов, которые могут отсылаться непосредственно вовлеченными во взаимодействие сервисами. Это значительно упрощает архитектру приложения в целом, но приводит к существенным затратам на передачу информации (никогда не используйте синхронные запросы, если хотите чтобы ваше MSA-приложение работало достаточно быстро!). При достаточно сложном приложении без создания менеджера не обойтись.
Для увеличения стабильности работы даже простого приложения лучше реализовать менеджер сообщений. Такой менеджер должен принимать асинхронный запрос от одного сервиса и передавать его другому. Он может быть реализован на сокетах, веб-сокетах, или любой другой удобной технологии. Запросы лучше хранить в очередях. При таком подходе появляется простой инструмент мониторинга взаимодействия сервисов между собой, даже если сейчас, на первый взгляд, нам этого и не надо.
Создание менеджера сообщений подразумевает, что его интерфейс должен быть стандартным и поддерживаться всеми сервисами продукта. С другой стороны, использование различных интерфейсов обмена сообщений у различных сервисов приведет к излишнему усложнению кода. Единый интерфейс так же подразумевает, что его дизайн должен быть готов перед началом кодирования.
"Мы делили апельсин..."
Теперь давайте рассмотрим основополагающую идею MSA: взаимодействие относительно независимых сервисов между собой.
Из плюсов стоит отметить возможность подмены одного сервиса другим без необходимости переустанавливать все приложение. Из недостатков — сервисы должны быть а) достаточно маленькие, б) достаточно автономные.
Решением здесь может быть правильное разбиение кода по сервисам. Причем деление должно быть не как в макроприложении, по функционалу (UI, Network, Back-End computing, DB), а по бизнес логике, например: обработка запроса на вход в систему, составление отчета по продажам, построение графика по данным из базы. Такие функционально-законченные модули становятся действительно независимыми и их применение становится очевидным. Кроме того, общий функционал приложения может быть легко и безболезненно расширен либо изменен.
Как его тестировать?
Если с макроприложением в плане тестирования было все понятно, то что же делать здесь? Куча сервисов, каждый из них может "дергать" множество других, данные хаотично пересылаются между сервисами… Кошмар! Но так-ли это?
Если мы все сделали правильно, то у нас есть приложение, которое:
- Состоит из набора функционально законченных сервисов
- Взаимодействие между сервисами происходит через менеджер сообщений
- Интерфейс сообщений стандартный у всех сервисов.
С точки зрения ручного тестирования, работа с каждым сервисом в отдельности является огромной головной болью. Зато какой простор для автоматизации!
Прежде всего давайте подключим логгер к нашему менеджеру сообщений, чтобы получить четкий и понятный лог работы каждого сервиса, при этом взаимодействие сервисов между собой так же становятся прозрачным. А значит мы можем быстро выявить проблемный сервис и при необходимости его откатить. В случае WEB-приложения можно реализовать мониторинг, который будет в режиме реального времени сообщать нам о возникших проблемах.
Т.к. интервейс сообщений у нас стандартный, нам не надо подстраиваться под каждый сервис в отдельности, достаточно использовать набор известных пар "запрос-ответ", например, из той же БД. А это так всеми любимый DDT (Data Driven Testing, не путать с рок-группой и/или пестицидом!), что приводит нас к потрясающей масштабируемости и производительности.
По условию задачи каждый сервис у нас — отдельная функционально-законченная единица. Совсем как функция или метод в макроприложении. Логично, если на каждый сервис будет написан набор "unit"-тестов. В кавычках, потому что мы тестируем не методы и функции, а сервисы с несколько более сложным функционалом. И опять-таки, нет совершенно никакой необходимости эмулировать действия пользователя, достаточно сформировать корректный REST-запрос. После реализации этого пункта можно сказать, что acceptance тесты разработаны для каждого сервиса. Более того, здесь опять напрашивается DDT — один тест применяется к разным сервисам, меняются только наборы входных/выходных данных.
Тестовый стенд
Таким образом у нас очень быстро набралось неимоверное количество тестов, которые надо где-то запускать. Естественно, что тестовый запуск на одном сервере будет занимать достаточно долгое время, что нас никак не устраивает.
Для WEB-приложений решение очевидно: можно разворачивать отдельный пре-конфигурированный сервер для каждого запуска. Это не уменьшит нагрузку на сервер, но позволит разделить тестируемые сервисы между собой. Если запуск проводится в контролируемой среде, где источником новых багов будет только тестируемый сервис, то можно существенно сократить набор запускаемых тестов. Это очень важно на стадии разработки — когда разработчик получает возможность проверить свой функционал во взаимодействии с другими сервисами, не особо отвлекаясь на запуск полного набора тестов на своей машине.
Полное интеграционное тестирование можно при этом запускать, допустим, раз в сутки или при наличии достаточно большого количества изменений в сервисах.
Тестирование локальных приложений проводим так же, но на различных виртуальных машинах. Для этого очень удобно использовать облачные сервисы. При этом для уменьшения времени, необходимого на разворачивание системы, можно заранее подготовить уже сконфигурированную ОС с предустановленным набором инструментов.
Выводы
MSA — очень интересная и гибкая архитектура как для разработки, так и для тестирования. При правильном балансе простоты и универсальности, четком понимании структуры приложения, можно получить хорошую приозводительность при минимальных трудозатратах.
Однако если сделать прекос в ту или иную сторону, то можно зарыться в дебри трудно поддерживаемого кода с потерей всех преимуществ, предоставляемых MSA, при ухудшении общей производительности приложения.
Важно понимать, что для успешной и эффективной автоматизации тестирования MSA-приложений нужно четкое и плотное взаимодействие команд разработчиков и автоматизаторов между собой.
Что почитать:
Микросервисы (Microservices)
Преимущества и недостатки микросервисной архитектуры
Microservices. Как правильно делать и когда применять?