Мок-сервер для автоматизации мобильного тестирования

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

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

Чтобы не трогать код реального проекта (под NDA), для наглядности дальнейшего изложения я создал простой REST-клиент под Android, позволяющий отправлять на некий адрес HTTP-запросы (GET/POST) с необходимыми мне параметрами. Его-то мы и будем тестировать.
Код приложения-клиента, диспатчеров и тестов можно скачать с GitLab.

Какие существуют варианты?

Подходов к мокированию в моем случае существовало два:

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

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

Выбор мок-сервера

Разных инструментов существует много. Я пытался работать с несколькими и почти в каждом столкнулся с определенными проблемами:

  • Mock-serverwiremock – два мок-сервера, которые я так и не смог нормально запустить на Android. Поскольку все эксперименты происходили в рамках живого проекта, время на выбор было ограничено. Поковырявшись с ними пару дней, я оставил попытки.
  • Restmock – это обертка над okhttpmockwebserver, подробнее о котором речь пойдет далее. Выглядела она неплохо, запустилась, но разработчик этой обертки спрятал “под капотом” возможность задания IP-адреса и порта мок-сервера, а для меня это было критично. Restmock стартовал на каком-то случайном порту. Ковыряясь в коде, я увидел, что при инициализации сервера разработчик использовал метод, который задавал порт случайным образом, если не получал его на вход. В принципе, можно было наследоваться от этого метода, но проблема была в приватном конструкторе. В итоге от обертки я отказался.
  • Okhttpmockwebserver – попробовав разные инструменты, я остановился на мок-сервере, который нормально собрался и запустился локально на устройстве.

Разбираем принцип работы

Текущая версия okhttpmockwebserver позволяет реализовать несколько сценариев работы:

  • Очередь ответов. Ответы мок-сервера складываются в очередь, работающую по принципу FIFO. Неважно, к какому API и по какому пути я буду обращаться, мок-сервер по очереди будет выкидывать сообщения, заложенные в эту очередь.
  • Диспатчер позволяет создать правила, определяющие, какой ответ отдавать. Допустим, запрос пришел по URL, содержащему некий путь, например /get-login/. По этому /get-login/ мок-сервер и отдает единичный, заранее заданный ответ.
  • Request Verifier. Опираясь на предыдущий сценарий, я могу проверять запросы, которые отправляет приложение (что в заданных условиях запрос с определенными параметрами действительно уходит). При этом ответ неважен, поскольку он определяется тем, как работает API. Этот сценарий и реализует Request verifier.

Рассмотрим каждый из сценариев подробнее.

Очередь ответов

Простейшая реализация мок-сервера – очередь ответов. До теста я определяю адрес и порт, где будет развернут мок-сервер, а также тот факт, что он будет работать по принципу очереди из сообщений – FIFO (first in first out).
Далее запускаю мок-сервер.

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

Реализация диспатчера

Диспатчер – это набор правил, по которым работает мок-сервер. Для удобства изложения я создал три разных диспатчера: SimpleDispatcher, OtherParamsDispatcher и ListingDispatcher.

SimpleDispatcher

Для реализации диспатчера okhttpmockwebserver предоставляет класс Dispatcher(). От него можно наследоваться, переопределив функцию dispatch по-своему.

Логика в этом примере простая: если приходит GET, я возвращаю сообщение, что это GET request. Если POST, возвращаю сообщение о POST request. В иных ситуациях возвращаю пустой запрос.

В тесте появляется dispatcher – объект класса SimpleDispatcher, который я описал выше. Далее, как и в предыдущем примере, запускается мок-сервер, только на этот раз указывается своего рода правило работы с этим мок-сервером – тот самый диспатчер.

Исходники тестов с SimpleDispatcher можно найти в репозитории.

OtherParamsDispatcher

Переопределяя функцию dispatch, я могу оттолкнуться от других параметров запроса для отправки ответов:

В данном случае я демонстрирую несколько вариантов условий.

Во-первых, в API можно передавать параметры в адресной строке. Поэтому я могу поставить условие на вхождение в path какой-либо связки, например “?queryKey=value”.
Во-вторых, данный класс позволяет залезть внутрь тела (body) запросов POST или PUT. Например, можно использовать contains, предварительно выполнив toString(). В моем примере условие срабатывает, когда приходит POST-запрос, содержащий “bodyKey”:”value”. Аналогично я могу валидировать header запроса (header : value).

За примерами тестов рекомендую обратиться к репозиторию.

ListingDispatcher

При необходимости можно реализовать и более сложную логику – ListingDispatcher. Тем же способом я переопределяю функцию dispatch. Однако теперь прямо в классе задаю дефолтный набор стабов (stubsList) – моков на разные случаи жизни.

Для этого я создал открытый класс RequestClass, все поля которого по умолчанию пустые. Для данного класса я задаю функцию response, которая создает объект MockResponse (возвращающую ответ 200 или некий иной responseText), и функцию matcher, возвращающую true или false.

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

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

При такой реализации диспатчера тесты остаются простыми. Я также стартую мок-сервер, только выбираю ListingDispatcher.

Ради эксперимента я заменил стаб на POST:

Для этого вызвал функцию replacePostStub от обычного dispatcher и добавил новый response.

В тесте выше я проверяю, что стаб был заменен.
Затем я добавил новый стаб, которого не было в дефолтных.

Request Verifier

Последний кейс – Request verifier – обеспечивает не мокирование, а проверку отправляемых приложением запросов. Для этого я точно так же стартую мок-сервер, реализовав диспатчер, чтобы приложение возвращало хоть что-то.
При отправке запроса из теста тот приходит в мок-сервер. Через него я могу получить доступ к параметрам запроса, используя takeRequest().

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

Итоги

В целом инструмент (okhttpmockwebserver) мне понравился, и я использую его на большом проекте. Безусловно, есть некоторые мелочи, которые я хотел бы изменить. Например, мне не нравится, что приходится стучаться по локальному адресу (localhost:8080 в нашем примере) в конфигах своего приложения; возможно, я еще найду способ все настроить так, чтобы мок-сервер отвечал при попытке отправить запрос на любой адрес.
Также мне не хватает возможности переадресации запросов – когда мок-сервер отправляет запрос дальше, если у него нет для него подходящего стаба. В данном мок-сервере такого подхода нет. Впрочем, до их внедрения и не дошло, поскольку на данный момент в “боевом” проекте не стоит такой задачи.

Автор статьи: Руслан Абдулин

Наши статьи по теме:

Все статьи

Связаться с нами

Мы свяжемся с вами в течение 24 часов.