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

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

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

    Для тех кто не знает, компонентные тесты — это тесты которые полностью изолированы от глобального окружения и позволяют проверить те или иные кейсы, которые 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_ADDR\
    local-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) тестов — также запускать их внутри контейнера и изолировать от остального окружения.
    «Колёса Крыша Маркет»
    77,00
    Компания
    Поделиться публикацией

    Похожие публикации

    Комментарии 15

      +1
      Чтоб ускорить скорость прохождения тестов в десятки раз, достаточно в самом начале разработки поставить Sleep(N minutes), а потом убрать.
      А если серьезно, мокая что то, скорее всего надо будет написать интеграционный тест.
      В предыдущем проекте было более 5000 сценариев, скорость выполнения 100-500 сценариев/сек в зависимости от машины. Но там отдельно тестировали сценариями бекенд, а для фронтэнда использовали силениум.
      В новом проекте с помощью этого github.com/GoogleChrome/puppeteer пытаемся объединить сценарии БЭ+ФЭ
        0
        Мы сейчас на основе этого же мок-сервера изолировано пытаемся прогонять frontend средствами selenium. А в сторону puppeteer не смотрел, спасибо.
        0
        Постепенно двигаюсь к пониманию того что наборы тестов должны быть поделены на несколько suite. Долгие, быстрые, минимальный регресс, подзадачные. Долгие собираются и гоняются раз в сутки или перед выкаткой например, могу длиться и час. Быстрые и минимал регресс гонять после каждого коммита, а подзадачные это мини сьют у каждого разработчика свой, где он включает только нужные тесты и при разработке гоняет только их, и только перед коммитом прогоняет локально быстрые и минимальный регресс. Пока у себя не внедрил, сейчас у нас есть по сути подзадачный, быстрый и медленный наборы.
          0
          Мы используем codeception и у каждого разработчика есть свой конфиг файл с настройками его окружения, а также есть возможность запускать тесты по файлово и группам.
          И еще сейчас, у нас есть 4 suite (Acceptance, Component, Unit, Newman, Smoke).
          Немного о newman: habr.com/company/kolesa/blog/353902
          0
          Еще одни моки github.com/php-vcr/php-vcr
            0
            также нередко приходилось их перезапускать, чтобы добиться зеленой сборки,

            вот в этом месте надо было фиксить проблему, а не мокая апи, если чтото замокано то функциональным тестом это больше назвать низя, и нужно писать ещё тесты которые без мока будут работать, а вашу проблему можно решить через докеры или деплой переделать так чтоб сервис был 99.9% времени в рабочем состоянии, с продакшеном же та же проблема когда релиз происходит
              0
              Тут дело такое…
              Где-то могут вестись какие-либо работы, может что-то произойти с базой.
              Но, замечания верные. Спасибо.
                0
                с базой это наверно самое сложное, как делать миграции и ничего не ломать, но как уже писал это нужно и для продакшена, хорошо если можно на выходных или в нерабочие часы задеплоить и не сломать ничего, но с ростом проекта это окно всё меньше и меньше и чем раньше начать деплой правильно делать тем лучше, говорят есть приложения которые годами работают без перебоев :)
                  0
                  У нас большое количество разработчиков, у каждого из них подняты локально тестовые окружения и все они собирают сборки в bamboo, что не может не сказаться на нагрузке на тестовый АПИ и другие сервисы, поэтому это всё дорого.
                0
                Тоже считаю что проблему изначальную не решили, а обошли.
                В первую очередь нужно добиться стабильности тестов.

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

                  Операции которые взаимодействуют и критичны для бизнеса покрыты всевозможными тестами

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

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

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое