Укрощаем зоопарк, или Тестируем с помощью собственных API-mocks

Как тестировать систему, если половина её компонентов — это «чёрные ящики» с уникальными протоколами, а стандартные API-mocks не справляются? С точки зрения готовых решений — тупик…
Мы столкнулись с этой проблемой и создали собственные API-mocks, которые не просто отвечают шаблонами, а ведут себя как настоящие компоненты системы. В этой статье — наш путь от идеи до работающего решения, которое можно адаптировать под ваши задачи.

Укрощаем зоопарк, или Тестируем с помощью собственных API-mocks

Содержание:

Что мы тестировали?

Продукт представляет собой сервер с веб-интерфейсом. Он управляет множеством клиентских устройств, подключающихся к нему по HTTP, NATS и самописным протоколам.

Все эти протоколы, кроме HTTP, поддерживают двунаправленную связь, то есть не только клиенты могут отправлять запросы к управляющему серверу, но и сервер может отправлять данные клиентским устройствам.

Укрощаем зоопарк, или Тестируем с помощью собственных API-mocks

Клиенты (A, B, C на схеме выше) могут иметь разные исполнения: виртуальная машина или программно-аппаратные комплексы (ПАК) с разными конфигурациями аппаратного обеспечения. Ближайшая аналогия — интернет вещей (IoT), где, например, умная колонка выступает в качестве управляющего устройства, к которому подключаются разнообразные устройства и датчики.

Ограниченность существующего подхода

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

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

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

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

Поиск решения

В основании классической пирамиды тестирования находятся юнит-тесты как самый стабильный и многочисленный класс автотестов. Чуть выше располагаются служебные тесты, которые уже не проверяют отдельные модули или сервисы приложения, как юнит-тесты, а нацелены на тестирование взаимодействия между ними (например, взаимодействие backend с базой данных). На верхушке пирамиды располагаются end-to-end тесты, самые трудоёмкие и нестабильные, но при этом максимально приближенные к действиям пользователя.

Укрощаем зоопарк, или Тестируем с помощью собственных API-mocks

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

Самой перспективной идеей для реализации этих тестов была изоляция тестируемого сервера от всех клиентских устройств и замена их специальными mocks. В таком случае тестировщик получает полный контроль над всеми присылаемыми на сервер данными, что позволяет полностью протестировать API.

Большинство существующих решений (Postman, Mockoon, WireMock, Apidog) нацелены на построение только HTTP-серверов с заранее заданными статичными ответами на запросы. Но хотелось найти единое решение, которое поддерживало бы всё разнообразие протоколов и позволяло динамически генерировать данные для отправки на сервер. Поэтому мы решили самостоятельно реализовать API-mocks (далее — эмуляторы), так как они эмулируют работу реальных устройств.

Первые шаги

В качестве основного стека технологий выбрали Python и Flask из-за простоты использования и высокой скорости разработки. Интерпретируемость Python позволяет быстро редактировать исходный код и легко менять данные, используемые им (в нашем случае, данные запросов эмуляторов).

Главное требование к эмуляторам — масштабируемость, то есть возможность создавать несколько экземпляров одного типа устройства на одном эмуляторе. Это позволило решить проблему подключения нескольких сотен устройств одного типа к тестируемому серверу.

Укрощаем зоопарк, или Тестируем с помощью собственных API-mocks

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

1. Управляющий HTTP-сервер, который принимал запросы от сторонних утилит (нагрузочные скрипты или автотесты) по настройке эмулятора в режиме реального времени. У всех эмуляторов были только две команды: создание нового экземпляра устройства и остановка ранее созданного.

2. Интерфейс взаимодействия с сервером, реализующий транспортный протокол. Он содержал статические данные, которые передавались серверу при взаимодействии по API.

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

Ниже представлена схема тестового стенда с использованием эмуляторов.

Укрощаем зоопарк, или Тестируем с помощью собственных API-mocks

Итоговая реализация

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

Динамические данные

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

Docker-контейнер

Ещё одним улучшением стало размещение эмуляторов в Docker-контейнерах и поставка их в таком виде другим членам команды, а именно QA-инженерам и backend-разработчикам. Теперь им не нужно узнавать у разработчика эмулятора ветку, в которой следует запускать эмулятор, или список новых библиотек, необходимых для работы новой версии эмулятора. Достаточно знать тег актуального Docker-образа.

Ещё один бонус использования контейнеризации — версионирование эмуляторов с помощью тегов Docker-образов.

Режимы работы

Самое полезное усовершенствование — реализация двух режимов работы эмуляторов: консольного и серверного. Режим выбирается при запуске.

Консольный режим

Эмуляторы оказались полезны не только в автотестах, но и в ручном тестировании, так как позволили QA-инженерам быстрее и проще настраивать тестовые стенды для специфических сценариев. Для большего удобства реализовали консольный интерфейс: он позволяет пользователю управлять работой эмулятора через командную оболочку, которая была реализована с помощью фреймворка Cmd стандартной библиотеки Python.

Команды в консольном режиме

ls — вывести список созданных экземпляров;

start — создать новый экземпляр;

stop <device_id> — остановить работу ранее созданного экземпляра;

show_info <device_id> — отобразить всю информацию об экземпляре в YAML-формате;

edit <device_id> — редактировать состояние конкретного экземпляра (у каждого эмулятора показатели состояния свои).

Серверный режим

Серверный режим изменился в двух аспектах, добавились:

  • HTTP-методы для управления эмулятором и созданными на нём экземплярами устройств. Они соответствуют консольным командам.
  • Динамическая документация к API в виде Swagger.

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

Итоговая схема работы эмулятора

Укрощаем зоопарк, или Тестируем с помощью собственных API-mocks

Пример использования эмулятора

Рассмотрим следующий пример, когда удобно использовать эмулятор. Представим, что нам необходимо протестировать сервер, к которому подключалось клиентское устройство. Клиент сообщил о своей текущей нагрузке на CPU (%) на тестовый сервер. Управляющий сервер уведомлял пользователя, что одно из клиентских устройств находится под повышенной нагрузкой: при достижении нагрузки более 90% отправлялось уведомление о критическом состоянии устройства.

На основании этой информации составили следующий тестовый сценарий:

1. Выставить значение нагрузки равным 91% (п. 1.1; 1.2 на схеме).

2. Проверить наличие предупреждения о критическом состоянии клиентского устройства в веб-интерфейсе управляющего сервера (п. 2 на схеме).

3. Выставить значение нагрузки равным 90% (п. 3.1; 3.2 на схеме).

4. Проверить отсутствие каких-либо предупреждений о нагрузке на клиентских устройствах в веб-интерфейсе управляющего сервера (п. 4 на схеме).

Укрощаем зоопарк, или Тестируем с помощью собственных API-mocks

Использование эмулятора упрощает подготовку перед выполнением стенда, так как в реальных условиях смоделировать ситуацию, когда на клиентском устройстве будет определённая нагрузка на CPU с точностью до одного процента, очень сложно. Эмулятор же позволяет изменить состояние устройства с помощью одной команды или HTTP-запроса. При этом добиться проверки необходимых комбинаций работы API между сервером и клиентом, как при тестировании на реальных устройствах.

Преимущества использования собственных API-mocks

Независимость тестирования

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

Ниже на диаграмме Ганта показана ситуация, когда команды разных продуктов разрабатывают интеграционные фичи независимо друг от друга.

Укрощаем зоопарк, или Тестируем с помощью собственных API-mocks

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

Простота масштабирования

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

Многофункциональность

В консольном режиме эмуляторы можно использовать не только в автоматизации (автотесты и нагрузка), но и в эксплоративном тестировании QA-инженерами.

Ускорение тестирования

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

Недостатки использования собственных API-mocks

Сложность поддержки

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

Необходимость end-to-end тестов

Не все баги могут быть найдены при использовании эмуляторов, так как часть из них зависит только от внутренней реализации реальных продуктов. Именно поэтому подобный подход в тестировании должен лишь дополнять end-to-end тесты с реальными устройствами, а никак не заменять их.

Заключение

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

Конечно, подход не идеален: поддержка эмуляторов требует ресурсов, а end-to-end тесты с реальными устройствами всё равно необходимы. Но когда нужно быстро проверить логику, не дожидаясь готовности всех компонентов — это мощный инструмент в арсенале QA-инженеров и разработчиков.

А как вы решаете проблему тестирования в условиях зоопарка протоколов? Делитесь опытом в комментариях — обсудим лучшие практики!

P.S. Чтобы всегда первыми видеть наши новые материалы, подписывайтесь на канал!

5
Начать дискуссию