Your browser is not supported anymore. Please update to a more recent one.


Download Chrome

Download Firefox

Download
Internet Explorer

Download Safari

Процесс разработки и тестирования демонов

30 марта 2015 | Константин Карпов
Сегодня мы поговорим о «низкоуровневых» кирпичиках нашего проекта — о демонах.
Определение из Википедии:
«де́мон — компьютерная программа в системах класса UNIX, запускаемая самой системой и работающая в фоновом режиме без прямого взаимодействия с пользователем».


Хоть это и не очевидно, но практически весь функционал сайта во многом зависит от работы этих программ. Игра в “Знакомства”, поиск новых лиц, центр внимания, обмен сообщениями, статусы, геолокация и многие другие вещи завязаны на тот или иной демон. Так что можно сказать, что они помогают людям по всему миру общаться и находить новые знакомства. Одновременно на сайте могут работать и взаимодействовать между собой несколько десятков демонов. Их корректное поведение является очень важной задачей, поэтому мы решили покрывать основной функционал демонов автотестами.

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

В качестве VCS у нас используется Git, для непрерывной интеграции — TeamCity, а в роли баг-трекера выступает JIRA. Для тестирования мы используем PHPUnit. Разработка демонов, как и остального сайта, ведется по принципу «фича ― ветка». Для того чтобы понять, что это, мы рассмотрим проекции нашего flow на Git и на JIRA.

Git flow


Работу в Git схематично можно описать картинкой:



Для работы над задачей от master-ветки форкают отдельную ветку task_n, в которой ведется вся разработка. Затем эта ветка и, возможно, несколько других сливаются в так называемый branch build_n.n.n (или просто билд), и только он уже попадает в master.

JIRA flow


Процесс в JIRA сложнее и включает в себя больше этапов. На рисунке ниже изображены основные из них.



Open -> In progress


Статус Open говорит о том, что задача есть, но к ней еще не приступили. Когда программист начинает работать и отправляет первые изменения на наш git-сервер (там появится ветка, содержащая в имени task_n, идентификатор в JIRA), происходит несколько событий. Во-первых, статус задачи становится In progress, а во-вторых, наша TeamCity создает отдельную сборку для этой ветки, которая запускается каждый раз при новом «пуше» разработчика. Благодаря стараниям релиз-инженеров и разработчиков, артефакты из сборки скопируются на машины нашей среды разработки, после этого запустятся все регрессионные тесты, а результат прогона отобразится в веб-интерфейсе TeamCity. Когда разработчик посчитает, что он закончил работу над задачей, он переводит тикет с ней в статус On Review.

On Review


Здесь задача проходит доскональное изучение со стороны коллеги. Если его взгляд не нашел к чему придраться, тикет переводится в статус In Branch QA.

In Branch QA


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

To Merge -> In Build


Когда разработчик и тестировщик считают, что сборка из ветки task_n работает стабильно и ожидаемо, в JIRA нажимается кнопка To Merge. В этот момент TeamCity «мержит» ветку разработчика в ветку build_n.n.n и запускает его сборку. Задача снова попадает к тестировщику, но уже со статусом In Build.

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

Когда тесты проходят, а демон стабилен, QA-инженер присваивает задаче статус In Build OK и она возвращается к разработчику. Билд демона с измененным поведением становится основным в среде разработки devel и проходит испытание боем.

In Build OK -> On Production


В какой-то момент в билдовой ветке накапливается много нужных изменений или появляются критически важные задачи, и разработчик решает, что пора двигать билд на продакшн. Нажимается кнопка Finish Build, и новая версия демона, благодаря нашим доблестным админам, начинает работать на машинах, отвечающих за настоящую работу нашего сайта. В это время билд мержится в master-ветку и создается новый билд с именем build_m.m.m, в который будут попадать все новые изменения. Ну, а тикету программиста поставят статус On Production.

Объединив две проекции всего процесса разработки, получим цикл, показанный ниже.



Подробнее о наших процессах разработки и плотной интеграции JIRA, TeamCity и Git можно прочитать в наших статьях тут, тут и тут.

Выполнение автотестов


Объект тестирования


Для начала давайте определимся с тем, что именно мы тестируем.

В нашем случае программа в общем виде представляет из себя некий бинарный файл, запускающийся из терминала. В качестве аргументов ему могут быть переданы различные параметры, влияющие на его работу: конфигурационные файлы («конфиги»), порты, лог-файлы, папки со скриптами и т.д. Чаще всего это как минимум конфиг, и строка запуска в этом случае будет выглядеть так:


>> daemon_name -c daemon_config


После запуска демон ожидает подключения на нескольких портах (сокет-файлах), по которым происходит общение с внешним миром. Формат может быть разным, начиная от текста или JSON'а и заканчивая protobuf'ом. Обычно поддерживается сразу пара из них. Также у некоторых демонов есть возможность сохранять свои данные на диск, а потом загружать их при запуске. Для разработки в основном используется C и C++, но с недавних пор добавились Golang и Lua.

В общем виде выполнение автотестов можно разбить на несколько этапов:

  1. Подготовка тестового окружения.
  2. Запуск и исполнение тестов.
  3. Чистка тестового окружения.

Итак, запускаем тесты.


>> phpunit daemon_tests/test.php --option=value


Подготовка тестового окружения


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

Часто бывает так, что демон во время работы общается с другими демонами — он может принимать или отдавать данные, получать или посылать команды от своих сородичей. В этом случае мы используем тестовые заглушки («моки») или запускаем реальных демонов. Когда все необходимое создано, запускается экземпляр демона с нашим конфигом и работающий в нашем окружением.

Помните, я говорил, что демон ждет подключения на портах? Последним шагом является создание коннектов к ним и проверка готовности демона к общению. Если все хорошо, то запускаются непосредственно тесты.

Запуск и исполнение


Большинство тестов можно описать алгоритмом:



В качестве send request может выступать один или несколько запросов, приводящих к какому-то определенному состоянию. Действия внутри get response — получение ответа, чтение записей в БД, чтение файла. Под assert же подразумевается большой спектр действий: это и валидация ответа, и оценка состояния демона, и проверка данных, пришедших в соседние демоны, и сравнение записей в БД, и даже проверка создания снапшотов с правильными данными.

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

На данный момент самой сложной и интересной (на мой взгляд) является область тестирования межпрограммного взаимодействия «демон — демон». В основном это связано с тем, что процесс общения зависит от многих факторов: внутренней логики, внешних запросов, времени, настроек в конфиге и т.д. Совсем весело становится, когда наш объект может общаться с несколькими разными демонами.

Чистка тестового окружения


Как бы не закончились тесты, после себя нужно чистить. В первую очередь мы закрываем коннекты, останавливаем демон и всех связанных с ним моков или соседних демонов. Затем удаляются все созданные во время тестов файлы и папки, если они не нужны, и подчищаются БД.

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

На весь описанный процесс тестирования накладывается определенная особенность — распределение по разным машинам. Обычным сценарием сейчас является запуск тестов на одном сервере, а старт демона на другом. Ну, или на другом сервере в контейнере docker'а.

Вернемся к параметрам запуска тестов. Сейчас список состоит из 5-6 вариантов, и он постоянно растет. Все они направлены на облегчение процесса тестирования и предоставление возможности немного «кастомизировать» тесты, не залезая в код. Вот наш топ:

  • branch. Сюда передается имя тикета в JIRA, в рамках которого проводились работы над демоном. Тесты будут использовать сборку бинарного файла именно этой задачи;
  • settings. Сюда передается путь до ini-файла, в котором прописаны специфичные для тестов вещи. Это может быть версия билда, путь до дефолтного конфига, пользователь, от которого запускается демон, расположение необходимых файлов;
  • debug. Если этот флаг передан, тесты работают в режиме отладки. Для нас это значит, что имена временных файлов и папок будут удобочитаемы, что позволяет их легко выделять из остальных. Также все файлы, создаваемые во время прогона тестов, не будут удаляться — это позволяет при необходимости воспроизводить падающие тесты;
  • script. Когда этот флаг присутствует, во время выполнения теста генерируются php-скрипт, повторяющий все действия теста, до момент его завершение или срабатывание ассерта. Этот скрипт полностью самостоятельный, что помогает при дебаге демона.
На сегодня это все, о чем мы хотели бы рассказать, и с удовольствием переходим к вопросам в комментариях.

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