IoT там, где вы не ждали (часть 3) — Построение имитационной модели

Как я уже рассказывал в прошлой части, при разработке IoT-проекта, протоколы взаимодействия с девайсами — вещь довольно нестабильная, и шансы потерять связь с тестовыми устройствами после обновления прошивки были довольно большие. Разработкой занималось несколько команд, и было жесткое требование — не терять возможность тестировать бизнес-слой приложения, даже если перепрошивка устройств переломает весь флоу работы с датчиками.

image

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


Также плюсом модели было то, что бизнес никогда не купит большую партию устройств только для проверки гипотезы. Например, команда бизнес-анализа решила, что предсказание времени заполнения контейнера должно работать по-другому. И для проверки их гипотезы никто не побежит покупать 10 000 датчиков.

Разработка имитационной модели


Сама имитационная модель выглядела следующим образом:

image

Состояние и поведение мусорного бака описывается обычной state-машиной. Сначала мы инициализируем state-машину состоянием `EMPTY (level = 0)` и можем выполнять над ней некоторые действия, то есть закинуть мусор в контейнер. Теперь нужно определиться, остается ли контейнер пустым `(level? MAX_LEVEL)` либо же он переполнен `(level >= MAX_LEVEL)`. Если второе, то состояние меняется на `FULL`.

Из полного контейнера кто-то может выгрузить мусор, либо же дворник пришел наводить свои порядки, и нам надо решить, в какое состояние переходить. За выбор действия отвечает стэйт `CHOICE` — в терминологии state-машины, что-то похожее на блок if.

Еще контейнер может гореть, и тогда состояние state-машины меняется на `FIRE`. Также контейнер может падать, и его состояние становится `FALL` (в докладе я рассказывал о том, какими неожиданными причинами могут быть вызваны падения контейнеров). Но есть еще одно состояние `LOST`, которое допустимо из любого другого стейта — оно устанавливается при потере связи.

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

По факту получилось, что вероятность событий зависела от времени суток, потому что:

  • перевозчики не работают ночью;
  • люди выбрасывают больше мусора в определенные часы (утром перед работой и вечером).

Поэтому мы сделали для команды бизнес-анализа возможность настраивать поведение имитации. Можно было задать вероятность того или иного события в определенное время суток.

Простой и наглядный stress test


Сама имитация имеет много преимуществ, и одно из них — дешевое нагрузочное тестирование. Дешевое, потому что имитация — это, по сути, отдельный поток, который запускает state-машину, применяет к ней события, и сами события отправляются на реальный сервер.

Поэтому имитация для бэкенда вообще ничем не отличается от реального датчика. И если нам нужно запустить 1000 датчиков, запускаем 1000 потоков и работаем. Кроме того, имитация прекрасно масштабируется.

image

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

Такой сервер должен был быть многопоточным, поскольку много входных датчиков. и эту часть тоже нужно протестировать отдельно, используя performance-тесты. Можно было взять JMeter (написать типичный тестовый сценарий), JMH / JCStress (протестировать изолированные части и сделать более тонкий бенчмарк), либо что-то свое. Когда вы принимаете решение в подобной ситуации, то советую послушать профессионалов, например, Алексея Шипилева. На JPoint 2017 он очень круто рассказал о том, как бенчмаркать разные вещи и о чем нужно думать, когда занимаетесь тестированием производительности.

Мы выбрали вариант сделать что-то свое, поскольку в проекте был нетипичный подход к QA — у нас нет отдельной команды тестировщиков, и команда бэкенда сама тестировала функциональность. То есть человек, написавший socket-сервер, должен был сам покрыть код обычными юнитами, интеграционными и performance-тестами.

У нас был небольшой инструмент, который позволял быстренько описать сценарий нагрузки и запустить его в нужном количестве параллельных потоков:

StressTestRunner.test()
                .mode(ExecutionMode.EXECUTOR_MODE)
                .threads(THREADS_COUNT)
                .iterations(MESSAGES_COUNT)
                .timeout(5, TimeUnit.SECONDS)
                .run(() -> sensor.send(MESSAGE));

Awaitility.await()
          .atMost(5, TimeUnit.SECONDS)
          .untilAsserted(() ->
            verifyReceived(MESSAGES_COUNT)
          );

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

Эмуляция сетевых проблем


С помощью имитации мы смогли симулировать как некачественную, так и специфичную работу с сокетами. GSM-симки в датчиках не имеют «белых» IP-адресов, и мы могли получить в течение дня по 50 раз данные с разных IP. И часто бывало так, что соединение открывалось, мы начинали передавать данные, потом меняется IP-адрес, и сервер открывает новое соединение, не закрывая старое. Если бы мы не брали это в расчет, то за пару дней у нас кончились бы свободные порты на сервере.

image

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

image

Это только часть сценариев, которые можно заложить в модель.

Выводы


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

Мы посмотрели довольно много различных аспектов разработки в IoT.

image

Если вы пропустили предыдущие две части этой статьи, то их можно найти тут:

IoT там, где вы не ждали (часть 1) — Предметная область и проблематика
IoT там, где вы не ждали (часть 2) — Архитектура приложения и тестирование IoT специфичных вещей

Github с инструментами для тестирования, о которых шла речь.

Немного рекламы. Как видите, на конференции по тестированию Heisenbug бывают очень разные доклады, например как мой про разработку и тестирование IoT. Надеюсь, вам было интересно! В этой статье я упомянул и конференцию JPoint, на которой я увидел доклады про тестирование нагрузки. В этот раз есть возможность купить билет и на Heisenbug и на JPoint и еще на 6 других конференций сразу. До встречи в онлайне!

© Habrahabr.ru