История о том, как мы ускорили тесты в 12 раз +28



Ускоряйте тесты, говорили они.

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

Для тех кто не знает, компонентные тесты — это тесты которые полностью изолированы от глобального окружения и позволяют проверить те или иные кейсы, которые unit тест не смог бы охватить.

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

Ведь, в этом случае от тестов вреда больше, чем пользы, но избавляться вовсе и “забить” на тесты, далеко не лучший вариант :) Тогда, тимлидом была организована небольшая микро-команда в лице:

  1. Тимлида
  2. Backend разработчика
  3. QA инженера
  4. Админа

Быстро скооперировавшись, мы разделили себе задачи и одной из них было — настроить среду для написания компонентных тестов. Таким образом и началось моё путешествие.

На момент начала разработки, у нас было 140+ функциональных тестов, которые запускались в несколько потоков под разные окружения (Frontend, Mobile, Backend) и проходили они ~5-7 минут; также нередко приходилось их перезапускать, чтобы добиться зеленой сборки. А падали эти тесты больше не из-за нового написанного кода, а из-за проблем в окружении, то есть, где-то API не ответил, где-то микросервис тестируемый упал и т.д. Это останавливало работу всего отдела, так как сборки запускались почти каждые 5-10 минут: кто-то пересобирал, кто-то пушил новый код…

После первой половины недели, пришли к тому, что будем “мокать” наше API и сторонние сервисы, что дало бы нам полностью изолированную среду тестирования. Но вставал вопрос: писать что-то своё или… Так вот, на этом “или” всё и завершилось — недолгими поисками, на своём пути я встретил — небольшую наработку в виде Mock сервера “http-api-mock”.

http-api-mock — легкий и не требующий установки mock сервер, написанный на языке Go с неплохой документацией.

Спустя сотни попыток запустить, а также вообще вникнуть в тему моков, мне всё же удалось переписать 1 функциональный тест, который создавал новое объявление на сайте и, пройдя все круги ада, убеждался, в том, что заголовок на странице соответствует заголовку в теле объекта.
Представьте себе, заработало! Переписанный тест оказался в 3 раза быстрее, чем предыдущий, так как здесь мы не проверяли создание, модерацию, а сразу же отдали из мока нужный объект объявления и выиграли на этом. Эта маленькая победа стала хорошим стимулом для дальнейшего развития этой темы, тем самым, спустя еще неделю, у нас был новый suite в codeception с названием “component”, который имел уже базовый helper класс для работы с нашим Mock сервером и запускался на тот момент у меня на песочнице.

Базовый класс-помощник умеет создавать объявление в виде json-файла в директории конфигов нашего mock-сервера, отдавать нужное объявление по id и т.д. Почти API.

Остальная магия ждала нас дальше — теперь оставалось настроить план сборок в bamboo. Чтобы наши тесты теперь проходили уже через наш CI&CD.

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

Для работы всей этой магии, нам пришлось добавить новые конфиг-файлы с новым адресом API и внешних сервисов, а также поднять копию базы mysql, ну и еще создать в build-плане новый таск с запуском нашего mock-сервера.

# Delete/Create network
docker network rm mock-kolesa-net;
docker network create --subnet=IP_ADDR/24 --gateway IP_ADDR_GATEWAY mock-kolesa-net;

# Docker run http-mock-kolesa
docker run --rm --name http-mock-kolesa -d -v ${CONFIG}/config/:/config -v ${CONFIG}/data/:/data --user $(id -u):$(id -g) --net mock-kolesa-net --ip IP_ADDRlocal-docker-hub.kolesa-domain.org:7979/build/http-mock-kolesa;

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

Время шло, тесты переписывались и вот 140 функциональных тестов превратились в 103 компонентных теста, которые проходят параллельно за ~30 секунд.



Из плюсов


Очень шустрые. За счёт того, что они полностью независимы от тестового окружения им не приходится ходить за данными куда-то далеко.

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

Легко писать. Собственно, в процессе переписывания, многое решалось копированием кода из старого функционального теста в новый компонентный и подготовки для него endpoint-ов в Mock сервере.

Из минусов


Поддержка. Теперь каждый разработчик, который внёс изменения в возвращаемый ответ для определенного endpoint-а в API, также идёт в репозиторий с конфигами для mock-сервера и правит ответ там.

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

Результаты:


Тесты
Было: 140+ функциональных тестов = 5-7 минут.
Стало: 103 компонентных тестов = ~30 секунд.

Стабильность сборок
Было: Каждая третья сборка падала из-за каких-либо проблем.
Стало: Падает только когда разработчик поломал логику какого-то метода.

В дальнейших планах у нас переписывание acceptance (gui) тестов — также запускать их внутри контейнера и изолировать от остального окружения.

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (11):

  1. AigizK
    /#19371184 / +1

    Чтоб ускорить скорость прохождения тестов в десятки раз, достаточно в самом начале разработки поставить Sleep(N minutes), а потом убрать.
    А если серьезно, мокая что то, скорее всего надо будет написать интеграционный тест.
    В предыдущем проекте было более 5000 сценариев, скорость выполнения 100-500 сценариев/сек в зависимости от машины. Но там отдельно тестировали сценариями бекенд, а для фронтэнда использовали силениум.
    В новом проекте с помощью этого github.com/GoogleChrome/puppeteer пытаемся объединить сценарии БЭ+ФЭ

    • Tumenbayev
      /#19371196

      Мы сейчас на основе этого же мок-сервера изолировано пытаемся прогонять frontend средствами selenium. А в сторону puppeteer не смотрел, спасибо.

  2. Aquahawk
    /#19371234

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

    • Tumenbayev
      /#19371256

      Мы используем codeception и у каждого разработчика есть свой конфиг файл с настройками его окружения, а также есть возможность запускать тесты по файлово и группам.
      И еще сейчас, у нас есть 4 suite (Acceptance, Component, Unit, Newman, Smoke).
      Немного о newman: habr.com/company/kolesa/blog/353902

  3. ggo
    /#19371432

    Еще одни моки github.com/php-vcr/php-vcr

  4. jetcar
    /#19372268

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

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

    • Tumenbayev
      /#19372376

      Тут дело такое…
      Где-то могут вестись какие-либо работы, может что-то произойти с базой.
      Но, замечания верные. Спасибо.

      • jetcar
        /#19372454

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

        • Tumenbayev
          /#19372920

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

  5. jehy
    /#19372730

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

    • Tumenbayev
      /#19372922

      Звучит очень интересно. Но, мы так далеко еще не заходили. Еще :)