Привет, Хабр! Меня зовут Елена Бабенко, я QA lead в одной из продуктовых команд Pangolin в Сбертехе. Мы разрабатываем СУБД, процесс это сложный и интересный, ведь помимо теории программирования и тестирования нам нужно разбираться и в работе PostgreSQL. Но здесь я хочу рассказать не про ежедневные задачи тестирования, а про релизы и роль выпускающего QA-инженера (обычно эту роль у нас берёт на себя один из QA‑лидов). Эта статья будет интересна тестировщикам, которые работают с крупными продуктами, чьи релизы занимают не час, а обычно пару недель.

СУБД Pangolin — один из таких больших продуктов, и примерно четыре раза в год у нас случается релиз. В каждую версию входят новые фичи, багфиксы, иногда — рефакторинг и другие улучшалки. В общем, большое количество нового кода. Конечно, каждая отдельная задача уже протестирована, но перед выходом в эксплуатацию необходимо стабилизировать уже всю сборку как цельный продукт.

От релиза к релизу мы улучшаем наши процессы и ускоряем разработку. У нас есть много Quality Gates для проверки — помимо стандартных юнитов, мы запускаем регрессы в различных вариациях (состояние сборки по умолчанию, кастомные комбинации, например с разными типами лицензий), обновления с предыдущих версий (здесь же и откаты), совместимость с разными ОС и другое.

Для многих запуск всех проверок — это муторная и неинтересная работа, которая ещё и занимает порядочно времени. А ведь потом надо все эти проверки сверить, проанализировать падения тестов, разобраться с багами (если такие будут), починить флакающие тесты... И в конце концов запустить это всё ещё раз для финального сбора отчётов.

Всё, что у нас было из инструментов для стабилизации релиза, — это таблица в Сonfluence со списком quality gates, Jenkins, в котором мы запускали эти quality gates, и руки, которыми мы это всё запускали:

Пример таблички для одной из версий: по горизонтали — список поддерживаемых ОС, по вертикали — имена проверок; в ячейки добавляем ссылки на отчёты
Пример таблички для одной из версий: по горизонтали — список поддерживаемых ОС, по вертикали — имена проверок; в ячейки добавляем ссылки на отчёты

К Jenkins никаких вопросов, к табличке (как одному из форматов отчётности), в целом, тоже, но вот ручной запуск… в каком веке мы живём?

Как-то раз я запускала все эти прогоны в конце рабочего дня, ошиблась при заполнении всего одного поля и на утро радостно перезапускала всё заново — это стало последней каплей.

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

Среди инструментов, которыми мы пользуемся каждый день, у нас есть «ночной директор» — это запуск прогонов на свежем develop по расписанию каждую ночь для того, чтобы понимать текущее состояние ветки. Список ночных quality gates хранится в .cron-файле, куда мы просто передаём нужные параметры для корректного запуска задачи в формате key1=value1;key2=value2

«Так это же ровно то, что мне нужно: один раз указать список всех QG и просто использовать в каждом релизе», — подумала я!

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

Идём думать дальше, то есть идём расспрашивать DevOps, как я могу реализовать запуск нужного списка.

Здесь уже я получаю финальную зацепку для реализации.

Кроме ночного директора у нас в команде есть ещё один инструмент: запуск нужных проверок в pull request по ключевым словам. Например, вы разработали и протестировали сложную фичу, вам нужно влить весь свой код в develop, но перед этим нужно убедиться, что вы ничего неожиданно не поломали. Пишете в комментарии к pull request волшебные слова run full develop — et voila, через несколько часов сможете посмотреть в своей ветке полный набор проверок и убедиться, что вы всё сделали корректно.

Механизм работы ключевых слов был известен только DevOps, а мы, QA, обычно приходили со словами «добавьте такую-то проверку в такой-то список», и всё. Но добавлять сразу 80 новых сценариев и думать, как их запускать, никто за меня не будет — у всех свои задачи. Поэтому я пошла выяснять.

Оказалось, что все сценарии для запуска ключевых слов хранятся в отдельной базе данных (не даром мы СУБД разрабатываем). И я могу создать свой собственный ключ, к которому будут привязаны нужные сценарии.

Каждый сценарий описан в трёх таблицах:

  1. scenario: информация о сценарии, его виде и версии продукта, под которую он настроен.

  2. build: прогоны, входящие в сценарий. Связь c таблицей scenario через внешний ключ scenario_id.

  3. build_params: параметры прогонов. Связь с таблицей build через внешний ключ build_id.

privileged_user_groups не имеет связей с остальными таблицами на уровне БД, но информацию из неё мы используем для ограничения прав на вызов определённых сценариев.

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

Для добавления сценариев использую такие запросы:

INSERT INTO scenario (scenario_id, scenario_name, product_version)
VALUES (generate_uuid(), 'run release test', '0.0.0')

Я создала сам сценарий (в product_version надо указывать актуальную версию продукта, но я пока тестирую свой подход, поэтому оставляю 0.0.0). Теперь добавлю в него проверку (или сборку — это то, что будет запущено в Jenkins):

INSERT INTO build (build_id, build_name, scenario_id)
VALUES (generate_uuid(), 'run regress sberlinux',
         (SELECT scenario_id FROM scenario WHERE scenario_name = 'run release test'));

Здесь моя проверка называется run regress sberlinux, название должно отражать её суть, чтобы потом в отчёте не запутаться и найти нужную проверку. В этой сборке, получается, будет гоняться весь регресс на ОС Sberlinux.

Теперь распишем параметры сборки для корректного запуска в Jenkins:

INSERT INTO build_params (build_id, parameter_type, parameter_name, parameter_value) 
         SELECT
                   (SELECT build_id FROM build WHERE build_name = 'run regress sberlinux'),
                   t.parameter_type,
                   t.parameter_name,
                   t.parameter_value
         FROM (VALUES
                   ('bool', 'os_sberlinux8', 'true'),
                   ('bool', 'test_sk1', 'true')
         AS t(parameter_type, parameter_name, parameter_value);

Здесь нужно указать все параметры для запуска задачи, в этом запросе я добавила ОС и блок тестов регресса для запуска. Параметров может быть больше, здесь надо отталкиваться от каждой конкретной проверки.

Повторю это всё ещё около восьмидесяти раз для всех проверок…

Когда у меня уже было 83 проверки в одном сценарии, стало понятно: в каждом релизе мы не создаём всё с нуля — мы клонируем сценарий с прошлого релиза и меняем только версию и пару параметров; возможно, добавляем 1-2 новых сценария. Для этого можно использовать процедуру клонирования, которая генерирует новые ID и копирует все вложенные сборки и параметры:

CREATE OR REPLACE PROCEDURE qg_pull_requests_multiversion.clone_scenario(IN scenario_name character varying, IN scenario_original_product_version character varying, IN scenario_new_product_version character varying)
 LANGUAGE plpgsql
AS $procedure$
DECLARE
    original_scenario_id UUID;
    new_scenario_id UUID;
    original_build_id UUID;
BEGIN

    -- Находим оригинальный сценарий
    SELECT id INTO original_scenario_id
    FROM qg_pull_requests_multiversion.scenario
    WHERE "name" = scenario_name
      AND product_version = scenario_original_product_version;
    IF original_scenario_id IS NULL THEN
        RAISE EXCEPTION 'Scenario with name "%" and product_version "%" not found', scenario_name, scenario_original_product_version;
    END IF;

    -- Генерируем новый ID для сценария
    new_scenario_id := gen_random_uuid();

    -- Клонируем сам сценарий с новой версией продукта
    INSERT INTO qg_pull_requests_multiversion.scenario (
        id, "name", pattern, product_version, created_date, privileged_user_groups
    )
    SELECT
        new_scenario_id,
        "name",
        pattern,
        scenario_new_product_version,
        NOW(),
        privileged_user_groups
    FROM qg_pull_requests_multiversion.scenario
    WHERE id = original_scenario_id;

    -- Клонируем все build'ы, привязывая их к новому сценарию
    FOR original_build_id IN
        SELECT id FROM qg_pull_requests_multiversion.build
        WHERE scenario_id = original_scenario_id
        ORDER BY index_number
    LOOP
        INSERT INTO qg_pull_requests_multiversion.build (
            id, scenario_id, "name", index_number, job, created_date, sequential_thread, primary_build
        )
        SELECT
            gen_random_uuid(),
            new_scenario_id,
            "name",
            index_number,
            job,
            NOW(),
            sequential_thread,
            primary_build
        FROM qg_pull_requests_multiversion.build
        WHERE id = original_build_id;
    END LOOP;

    -- Клонируем параметры всех build'ов

    -- Используем JOIN через маппинг по имени и индексу, так как ID теперь разные
    INSERT INTO qg_pull_requests_multiversion.build_params (
        id, build_id, parameter_type, parameter_name, parameter_value, created_date, default_parameter_value
    )
    SELECT
        gen_random_uuid(),
        new_b.id,
        bp.parameter_type,
        bp.parameter_name,
        bp.parameter_value,
        NOW(),
        bp.default_parameter_value
    FROM qg_pull_requests_multiversion.build_params bp
    JOIN qg_pull_requests_multiversion.build old_b ON bp.build_id = old_b.id
    JOIN qg_pull_requests_multiversion.build new_b
        ON old_b."name" = new_b."name"
        AND old_b.index_number = new_b.index_number
        AND new_b.scenario_id = new_scenario_id
    WHERE old_b.scenario_id = original_scenario_id;

    RAISE NOTICE 'Scenario "%" successfully cloned from version "%" to "%"', 
                 scenario_name, scenario_original_product_version, scenario_new_product_version;

END;
$procedure$
;

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

  • юнит-тесты на всех ОС;

  • регресс на всех ОС;

  • кастомные регрессы на нужных ОС (комбинации различных специфический фич);

  • обновления и откаты на основной ОС;

  • обновления и откаты на других ОС.

DevOps помогли и сделали отдельную задачу в Jenkins, в которой я теперь могу выбрать ветку и наборы QG для запуска (хоть один набор, хоть все сразу).

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

Итак, как поменялся процесс стабилизации релизной сборки. Было:

  • Определяем список необходимых QG.

  • Обновляем отчётную таблицу под актуальный список QG.

  • Руками запускаем нужные QG (два часа на это убить можно смело).

  • Анализируем отчёты и так далее.

Стало:

  • Определяем список необходимых QG.

  • Обновляем отчётную таблицу и сценарий в базе данных под актуальный список QG.

  • Идём в задачу для автоматического запуска и нажимаем пару кнопок.

  • Анализируем отчёты и так далее.

Да, шагов осталось столько же, но я сэкономила минимум день на каждый следующий релиз (и избавилась от муторной ручной работы).

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

Попытки были. Мой коллега, например, делал тестовую задачу, в которой можно было галочками выбрать нужные параметры для запуска и список ОС, но это всё ещё был около ручной запуск (пусть и большого количества проверок), поэтому, вероятно, решение не прижилось. Так сложились обстоятельства, что именно у меня эта «проблема» стала достаточно явной, чтобы взять и поменять процесс.

По итогам проделанной работы я могу сделать следующие выводы:

  • Конфигурация в БД — это не про разработчиков. QA-инженер с базовым знанием SQL (да и с помощью ИИ, куда без него) может самостоятельно управлять набором проверок без участия DevOps на каждом шаге.

  • Автоматизация рутины высвобождает время для реальной работы. Вместо двух часов кликов по Jenkins — два клика по мастер-задаче. Остальное время ушло на анализ проверок, которые нашли настоящие баги перед релизом.

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

  • Если ваша команда запускает больше 20 проверок перед релизом, то посмотрите, где можно вынести конфигурацию в базу и запустить по рычагу. Иногда достаточно одного ленивого инициативного QA.

Спасибо за внимание!