company_banner

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

    Сегодня мы поговорим о «низкоуровневых» кирпичиках нашего проекта — о демонах.
    Определение из Википедии:
    «де́мон — компьютерная программа в системах класса 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-контейнеров в тестах — отмечайте, какие темы вам будут интересны больше всего.

    Константин Карпов, QA инженер
    Badoo
    438.08
    Big Dating
    Share post

    Comments 14

      0
      Спасибо за статью. Скажите, а регрессии производительности вы на этом же этапе ловите?
        0
        Для того, чтобы полноценно проверить производительность демона нужно дать нагрузку, сопоставимую с нагрузкой боевых демонов. К сожалению, для того, чтобы сделать это в тестах ресурсов сейчас нет, но благодаря тому, что демон не сразу выкатывают на продакшен, многие проблемы находят на предварительных этапах — фаталы могут поймать и функциональные тесты, а на тестовом окружении devel также могут вскрыться проседания. Сама же выкладка на продакшен осуществляется в несколько шагов — сначала только для некоторых стран, и только потом уже по всему миру. Отдел мониторга, если ловит какие-то аномалии на одном из этапов, сразу сообщает об этом.
        0
        Не очень ясно, чем разработка и тестирования демона в вашем частном случае отличается от разработки любой другой программы, не-демона.

        А насчет чистки после возможных «фатальных» ошибок, вы не думали использовать готовые виртуальные машины?
        В простеньком велосипеде это может выглядеть как
        1. скопировать образ готовой и настроенной «чистой» виртуальной машины в из каталога образов на тестовую машину.
        2. Запуск виртуальной машины и подключение к ней по ssh, копирование и установка вашего нового демона
        3. Запуск и тестирование.
        4. Остановка виртуальной машины, удаление «отработанного» образа, или перемещение его в папку, если вдруг нужно будет посмотреть что там случилось.

        В случае тестирования демонов, можно образ создать без gui, совсем маленький, то есть ресурсов потребуется на порядок меньше, а качество и надежность тестов увеличится.
          0
          Вы правы, сам процесс имеет много чего, что можно встретить при разработке других продуктов — git, jira и т.д. Специфика, как обычно, в деталях — как мы собираем демоны, артефакты и как выкладываем, тестируем.
          Про виртуальные машины мы думали, но до недавнего времени мощностей для повсеместного внедрения в процесс не было. Сейчас мы ориентированы на использование docker-контейнеров — эксперименты уже проведены, результаты нам понравились и по-тихоньку начинаем использовать их в тестах.
          0
          Интересно бы было узнать, как тестируются обновления, которые меняют поведение работающих демонов в данный момент на продакшене. Внутреннее версирование? Токенизация?
            0
            Если кратко — прогоняется регрессия, проверяется новое поведение и пишутся тесты, выкладывается на devel, и, если все хорошо, отправляется на продакшен. Процесс выкладки немного описал выше в комментариях.
            Внутренне версирование есть.
            0
            Мне одному кажется, что заголовок не отражает сути статьи?
            было бы интереснее узнать от вас, какие языки используете и для каких задач, как работаете с БД, обрабатываете большие объемы данных, как высвобождаете память, с какими трудностями вам пришлось столкнуться и как вы их решили и почему именно так.
              0
              Статья является обзором всего процесса, поэтому технические детали были опущены. Скажите, что вам было бы интересней всего услышать в следующих статьях, а мы постараемся удовлетворить ваше любопытство.
                –2
                ну вот так видится процесса разработки с точки зрения QA: open, in progress… on prod!
                0
                Проблемным местом здесь является чистка после фатальных ошибок — в некоторых случаях могут оставаться временные файлы (бывает, даже демон продолжает работать).

                А можно поподробнее почему не получается их очистить автоматически, ведь воркспейс в любом современном CI можно полностью очистить от всего постороннего не прибегая к rm -rf /* :)
                Поэтому собственно:
                1) почему остаются временные файлы, они оказываются в неожиданных местах (корки?) или как?
                2) почему демон продолжает работать? Он не останавдивается по /etc/init.d/daemon stop или это не совсем тот демон в моем понимании? почему тогда не убивать его не совсем «gracefully» (sigint, sighup, sigkill)?

                И еще небольшой вопрос, есть ли у вас много-функциональные, так сказать интеграционные тесты? т.е. что-то происходит в веб интерфейсе, какая-то информация попадает в демон, там обрабатывается, что-то изменяется в веб интерфейсе?
                  0
                  Когда я говорил про фатальные ошибки, я имел в виду ошибки самих тестов — т.е. там где логика становится неправильной и php вызывает фатал. А так как все временные файлы чистятся и демон останавливается в деструкторах тестов, то при фатале все это не вызывается.
                  Нет таких тестов у нас нет, имхо, сложность построения не окупится.
                    0
                    Т.е. я правильно понимаю, проблема в том, что после фатала php падает и не вызывает tearDown (phpunit) или его аналог?
                    А пробовали ловить фатал в register_shutdown_function, и в ней вызывать tearDown?
                      0
                      да, проблема действительно в этом. Из-за некоторых технических особенностей, кунг-фу с register_shudown_function не работает.
                      Кстати, был не прав насчет больших интеграционных тестов — коллеги подсказывают, что отдел веб-тестирования имеет такие тесты и использует.
                    0
                    1. Воркспейс очистить можно, да, но некоторые демоны могут писать временные файлы не в директории воркспейса (захардкожены в демоне). Про эти пути известно в тестах конкретного демона.
                    2. Это не совсем тот демон, да (но очень похож). Если фаталов нет, то примерно так и происходит — демон останавливается по sigterm (для некоторых команда остановки отличается).

                    Есть и интеграционные, и системные тесты — это селениум-тесты сайта на девеле + отдельная ветка автотестов, ты должен помнить :) Правда, там тесты не на все демона, а лишь на некоторые, но тесты периодически дополняются.

                  Only users with full accounts can post comments. Log in, please.