Привет, Хабр! Меня зовут Елена Бабенко, я 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 новых сценариев и думать, как их запускать, никто за меня не будет — у всех свои задачи. Поэтому я пошла выяснять.

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

Каждый сценарий описан в трёх таблицах:
scenario: информация о сценарии, его виде и версии продукта, под которую он настроен.build: прогоны, входящие в сценарий. Связь c таблицей scenario через внешний ключscenario_id.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.
Спасибо за внимание!
