Привет, Хабр! Меня зовут Богдан Бурков, я старший инженер по тестированию в мобильном приложении для продавцов Ozon Seller. В этой статье я хочу поделиться историей, как мы с коллегой, ведущим инженером по тестированию Владиславом Поповым (привет, Влад!), 
улучшали наш релизный процесс и что у нас из этого получилось.

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

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

Определимся с точкой отсчёта

Знакомая боль для многих команд: релизное тестирование забирает значительную часть времени. В день релиза на доске уже висят бизнес-задачи, нужно закрывать бэклог, выполнять текущие активности, а кто-то из команды может быть в отпуске или на больничном. И ко всему этому добавляется релиз.

Мы, как и многие, задумались: можно ли сделать процесс быстрее и при этом не потерять в качестве?

Для начала зафиксируем текущие цифры:

Точка отсчёта
Точка отсчёта

Далее кратко формулируем задачу, которую планируем решить:

  • Оптимизировать время релизного тестирования — cократить время проведения и освободить ресурсы для команд за счёт автоматизации процессов.

А теперь предлагаю разобрать, из чего вообще состоит наш релизный процесс и где, как мы обнаружили, причины потери времени.

Где мы нашли неожиданный источник задержек

Опираясь на личный опыт проведения процесса релизного тестирования, мы разложили его на ключевые точки. Кратко опишу, как он у нас устроен.

В состав релизной команды у нас входят две группы QA-специалистов: одна проводит тестирование на платформе Android, а вторая на iOS, — а также release-owner (далее — RO), ответственный за проведение процесса релизного тестирования.

Процесс релизного тестирования у нас разделён на два этапа:

  1. Регресс (regression)— прохождения ручных кейсов на тестовом контуре от каждой команды, содержащих проверки как нового функционала, который катится на текущей неделе, так и существующего критичного.

  2. Смоук (smoke)— проводятся проверки критичного функционала на проде.

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

Мы понимаем, что основную часть времени релизного тестирования занимают именно проверки. Над этой проблемой мы уже поработали ранее: сократили количество тест-кейсов нового функционала (желательно не более 10), тщательно ревьюим их, чтобы исключить дополнительные вопросы и уточнения со стороны участников релизной команды. А также увеличили автоматизацию проверок уже существующего функционала, что способствовало снижению количества проверяемых каждую неделю кейсов. 

Это дало свой эффект, но не решило проблему кардинально — релизное тестирование всё равно занимало почти рабочий день. И мы решили посмотреть на процесс с другой стороны: не на то, как проверяется, а на то, как это организовано. Нас интересовало, какие задачи выполняются вручную, сколько времени они забирают и кто за них отвечает. Так мы пришли к последнему участнику релизной команды, на котором замыкается большая часть организационной рутины, — Release-owner’у.

RO — это QA-специалист, который каждую неделю назначается по очереди из разных команд и отвечает за контроль проведения релизного тестирования.

В его обязанности входит:

  1. Подготовка к регрессу и смоуку: ручной сбор прогонов с тест-кейсами для прохождения участниками релизной команды, а также размещение сообщения в канал о старте.

  2. Контроль прохождения кейсов: недопущение задержек по таймингам, своевременно оповещение о необходимости ускорения (тестирования) или перехода к следующему этапу.

  3. Контроль разбора прогонов автотестов дежурными: отслеживает, чтобы все команды проверили свои автотесты, которые прогонялись на текущей релизной ветке.

  4. Сбор метрик производительности на stage и prod-контуре: проверка отправки и сбора метрик на таргет-экранах.

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

И ключевой момент: на данные задачи аналогично тратится время релизного тестирования.

Именно обязанности RO мы и решили сделать нашей первой целью для автоматизации. 

Три активности, которые мы автоматизировали 

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

Подготовка к проведению регресса и смоука.

Подготовка к релизу для RO начинается в пятницу. Как я упомянул выше, в неё входит создание прогонов с тест-кейсами, содержащими проверку как нового функционала, который катится в текущем релизе, так и существующего критичного. И вот здесь начинаются проблемы.

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

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

Наконец получив отмашку от лидов, RO приступает к ручному сбору прогонов с кейсами в Test Management System (далее TMS) и размещает сообщение в релизный канал о начале регресса в понедельник (ссылка на прогон, правила проведения). А поскольку ветку отрезают поздно вечером, ссылку на сборки он прикладывает только в понедельник, в день релиза.

Итого: на подготовку уходит примерно 15–20 минут. И это не считая постоянного переключения между задачами.

Для автоматизации подготовки мы добавили в релизный пайплайн две новые джобы. Одна отвечает за регресс, другая за смоук.  Внутри каждой лежит Python-скрипт, который выполняет следующее:

  1. Подключается к нашей TMS через gRPC.

  2. Запрашивает тест-кейсы из всех имеющихся согласно нужным тегам.

    Для фильтрации ручных тест-кейсов мы используем 3 типа тегов:

    • Regress — проверки, обязательные для каждого релизного тестирования.

    • Regress_once — проверки, актуальные только для текущего релиза (новый функционал, специфичные сценарии).

    • Smoke — исключительно для проверки на смоуке (критичный функционал на проде).

      И в зависимости от джобы скрипт использует разный набор тегов.

  3. Создаёт тест-план.

    Из полученных кейсов формируется тест-план с автоматически сгенерированным названием (платформа, прогон и номер релиза)

  4. Запускает прогон.

    На основе тест-плана создаётся ручной прогон, который используют тестировщики при проведении релизного тестирования.

  5. Отправляет сообщение в релизный канал. В сообщении — ссылка на прогон, на сборку и правила его проведения, а также тег релизной команды.

Пример сообщения
Пример сообщения
Пример реализации скрипта (упрощённый вариант):
class ReleaseLaunchCreator:
    """
    Класс для автоматического создания прогонов в TMS и отправки уведомлений.
    """

    def __init__(self):
        # Подключаемся к TMS через gRPC-клиент
        self.tms = TMSClient(env="prod")

    def create_regress_launch(self, platform: str, release_train: int) -> int:
        """Создаёт прогон для регресса"""
        # 1. Запрашиваем ID всех тест-кейсов с тегом "Regress"
        case_ids = self.tms.get_cases_by_tag("Regress")
        
        # 2. Создаём тест-план из этих кейсов
        plan_id = self.tms.create_test_plan(
            name=f"Regress {platform} - {release_train}",
            test_case_ids=case_ids
        )
        
        # 3. Создаём ручной прогон на основе тест-плана
        return self.tms.create_launch(
            name=f"Regress {platform} - {release_train}",
            test_plan_id=plan_id
        )

    def create_smoke_launches(self, platform: str, release_train: int, assignees: list) -> list:
        """Создаёт отдельные прогоны для каждого участника смоука"""
        # 1. Запрашиваем кейсы с тегом "Smoke"
        case_ids = self.tms.get_cases_by_tag("Smoke")
        
        # 2. Создаём общий тест-план
        plan_id = self.tms.create_test_plan(
            name=f"Smoke {platform} - {release_train}",
            test_case_ids=case_ids
        )
        
        # 3. Для каждого участника — свой прогон
        launches = []
        for assignee in assignees:
            launch_id = self.tms.create_launch(
                name=f"Smoke {platform} - {release_train} - {assignee}",
                test_plan_id=plan_id
            )
            launches.append(launch_id)
        return launches

    def send_regress_notification(self):
        """Главный метод: создаёт прогон и отправляет сообщение в чат"""
        # Получаем параметры из окружения CI
        platform = os.getenv("PLATFORM")
        version = os.getenv("RELEASE_VERSION")
        channel = os.getenv("RELEASE_CHANNEL")
        
        # Формируем номер трейна (например, 145)
        release_train = self._get_release_train(version)
        
        # Создаём прогон
        launch_id = self.create_regress_launch(platform, release_train)
        
        # Получаем ссылку на сборку
        build_link = BuildStorage().get_build_link(platform, version)
        
        # Формируем сообщение
        message = f"Регресс {platform} - трейн {release_train}\n" \
                  f"Прогон: https://tms-system.com/launches/{launch_id}\n" \
                  f"Сборка: {build_link}"
        
        # Отправляем в релизный канал
        Notifier.send(channel, message)

Джобы работают по разному:

Джобы для запуска скрипта
Джобы для запуска скрипта

Джоба для регресса запускается автоматически при отрезании релизной ветки. А джоба для смоука сделана ручной — её запускает RO (или любой участник релизной команды) после успешного завершения регресса. Это связано с тем, что время его начала зависит от скорости прохождения и успеха регрессионных тестов релизной команды. К тому же ручной запуск позволяет начать смоук, даже если Release-owner занят либо недоступен по каким-либо причинам.

Таким образом, теперь подготовка занимает 0 минут — RO больше не тратит время на ручное создание прогонов и оповещений. Всё происходит автоматически. А самое главное: в пятницу у него освобождается время, а в понедельник он может либо участвовать в прохождении кейсов в составе релизной команды, либо заниматься своими задачами, параллельно контролируя весь релизный процесс.

Контроль прогона автотестов 

У нас была одна джоба, содержащая автотесты всех четырёх команд, и их количество уже перевалило за 1500. И появилась проблема, что в случае большого количества падений или флаков RO приходилось перезапускать весь прогон и ждать повторно ещё примерно 25–30 минут. После этого ему необходимо было вручную тегать дежурных каждой команды для проверки своих тестов. И пока не будет получена отмашка от каждого, регресс не может быть завершён, а смоук — начат. 

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

Для этого мы:

  1. Разделили одну общую джобу с автотестами на несколько. Каждая из которых содержит набор автотестов отдельной команды.

  2. Настроили параллельный запуск.

  3. Добавили отображение сообщения с тегом дежурного каждой команды при размещении результатов каждого прогона.

  4. Для перезапуска только упавших тестов мы воспользовались возможностями нашего фреймворка, добавив в наши джобы с тестами переменную RERUN_FAILED_ONLY. Механизм работает так: после первого прогона всех автотестов сохраняются результаты. Если запускается повторный прогон, система анализирует эти данные и запускает только тесты, которые завершились ошибкой или флаком.

Отдельные джобы с тестами
Отдельные джобы с тестами

И результат был следующий:

  • Время ожидания одной джобы с тестами составляет примерно 5 минут.

  • Перезапуск позволяет перезапускать не все тесты, а только несколько, сокращая время ожидания повторного прогона.

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

Сбор метрик производительности

Для сбора метрик требовались ручные действия: открыть таргет-экраны на девайсе, выполнить скроллы, обновление и отправить события в ручку. И так по всем экранам на каждую платформу. Затем необходимо запустить джобу, которая проверяет, получены ли метрики в БД. Если нет — повторяем все действия заново.

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

Напомню, Release-owner — тестировщик одной из команд, и он отлично знает таргет-экраны своего функционала, а собрать метрики нужно со всех 10 по всему приложению. Таким образом, необходимо тратить время на поиск экранов чужого функционала, которые должны быть ещё заполнены корректными тестовыми данными. В итоге весь процесс на одну платформу мог занимать примерно 30–40 минут.

В рамках автоматизации текущей активности мы:

  1. Написали автотесты для обеих платформ с нужными тестовыми данными для сбора метрик. Они открывают таргет-экраны на тестовом и прод-контуре, выполняют действия для сбора метрик и отправляют их в ручку.

  2. Добавили две новые джобы в релизный пайплайн: одна запускается автоматически (для stage), вторая — вручную (для prod).

  3. Мы не проверяем метрики сразу после сбора — им нужно время, чтобы доехать до БД. Поэтому мы добавили в джобу анализа полученных метрик задержку в 15 минут. За это время параллельно выполняются обычные UI-тесты, которые тоже проходят по таргет-экранам и могут отправить недостающие метрики. Таким образом, к моменту проверки часть данных уже может быть собрана не только специализированными тестами, но и обычными.

  4. Настроили оповещения в релизный канал о прогоне каждой джобы.

Простой пример теста для таргет-экранов по сбору метрик производительности:

def test_performance_metrics_on_basket_page(page):

    with allure.step('Открыть раздел «Корзина»'):
		page.open_basket()

    with allure.step('Собрать метрики производительности'):
    	swipe_for_metrics(page, direction='down', step=0.6, duration=1, max_steps=6)
        swipe_for_metrics(page, direction='up', step=0.6, duration=1, max_steps=8)

    with allure.step('Отправить собранные метрики'):
        page.send_app_to_background()
        page.restart_app()

Здесь не обошлось без проблем в реализации, главная из которых — сбор метрик на prod для iOS. С Android всё было просто: у нас уже была джоба, которая собирает prod-сборку для эмулятора, и тесты на проде проходили успешно. А вот на iOS есть только джоба для симулятора, и она по умолчанию всегда смотрит на stage-контур. Мы рассмотрели несколько вариантов, включая создание отдельной джобы для iOS, но в итоге пошли другим путём: добавили в приложение поддержку аргументов запуска.

Теперь при старте приложения мы передаём аргумент в зависимости от окружения. Например, envStg=1 — приложение работает со stage, envStg=0 — с prod. Одна и та же сборка для симулятора может переключаться между контурами без дополнительных джоб

Джобы для сбора метрик производительности
Джобы для сбора метрик производительности

Теперь сбор метрик производительности у нас происходит полностью автоматически. В понедельник утром RO первым дело проверяет результаты прогонов сбора метрик на каждой платформе, иногда вручную дособирает недостающие метрики вручную, потратив примерно 5–10 минут. Так метрики собраны на stage-контуре. Во время смоука он запускает соответствующую джобу, проверяет результаты и завершает уже сбор и на prod-контуре.

Как сейчас изменился наш процесс релизного тестирования

Новый процесс релизного тестирования
Новый процесс релизного тестирования

А теперь давайте посмотрим, как проводится тестирование после всех изменений.

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

  • с тестами;

  • сбором метрик;

  • подготовкой прогона к регрессу и оповещением о его старте.

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

Release-owner в это время идёт проверять метрики. И его уже ждут отчёты: Android — 10/10, а iOS — 9/10 (например, одна метрика не собралась). Потратив немного времени, он открывает нужный экран руками и дособирает её.

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

А теперь главный вопрос, на сколько же у нас получилось сократить время нашего релизного тестирования? 

Ниже — таблица с ключевыми активностями, которые мы автоматизировали:

Активность

Было

Стало

Экономия

Подготовка к регрессу и смоуку

RO вручную собирал прогоны в TMS, проверял готовность кейсов и размещал сообщение в релизный канал

Скрипты автоматически создают прогоны  и отправляют уведомление

15–20 минут ручной работы → 0 минут

Контроль автотестов

Одна общая джоба на 1500+ тестов. При падениях или флаках приходилось перезапускать весь прогон и снова ждать 25–30 минут

Автотесты разделены по командам, запускаются параллельно;
Повторно запускаются только упавшие тесты

25–30 минут ожидания → около 5 минут на командную джобу + точечные перезапуски

Сбор метрик производительности

RO вручную проходил 10 таргет-экранов на каждой платформе. Если часть метрик не собиралась, действия повторялись

Метрики собирают автотесты на stage и prod;
RO проверяет результат и при необходимости дособирает только недостающие;

30–40 минут × 2 платформы → 5–10 минут × 2 + ручной досбор при необходимости

Итого

Старт в 10:00, завершение в 16:00–17:00

Старт в 10:00, завершение в 13:00–14:00

до 7 часов → до 4 часов (экономия до 3 часов, ≈45%)

Но экономия не ограничивается прямым временем на каждую активность. Мы получили ещё дополнительное ускорение за счёт следующего:

  • Параллельность. Раньше всё шло последовательно: сначала подготовка, потом регресс, потом автотесты, потом метрики. Теперь часть активностей выполняется параллельно.

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

  • Участие RO в тестировании. Освободившееся время он тратит на помощь в прохождении кейсов.

  • Прозрачность. Уведомления и отчёты приходят автоматически, команда тратит меньше времени на коммуникацию и поиск информации.

Итог

Релиз стартует в 10:00 и завершается в 13:00–14:00. В худшем сценарии — 4 часа. Сокращение — 3 часа (почти 45%).

Но не только в цифрах дело.

Автоматизация не исключила роль Release owner'а — она её изменила. Ранее его главными задачами было собрать прогоны, перезапустить тесты, собрать метрики и прочее. Теперь всё это обеспечивает наш «код».

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

И ещё несколько выводов из нашего опыта:

  • Автоматизируйте рутину — особенно ту, что спрятана в очевидных местах. Если действие повторяется от релиза к релизу, то попробуйте автоматизировать и положить его в CI. Обратите внимание не только на тесты, но и на организационные моменты: оповещения, сбор прогонов, проверку метрик, перезапуски. Именно там часто теряются часы.

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

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

Конечно, не всё из описанного подойдёт любой команде. У каждого свой стек, процессы и боли. Но надеюсь, наша история поможет кому-то присмотреться к своим релизам, найти точки роста и сделать первые шаги к автоматизации релизного тестирования. Успехов!

Комментарии (1)


  1. Dhwtj
    20.07.2026 12:15

    Настоящий QA должен уметь доказывать корректность изменений. Например, через типы и инварианты.

    Для этого он должен встать не в конце, а в начале цепочки изменений. Спроектировать изменения так, чтобы корректность доказывалась компилятором, стат анализом, параметрическими тестами, авто тестами по кейсам и как самый неэффективный способ - ручными Е2Е тестами.

    QA вроде мог бы ставить задачи к разработчику: спроектируй [эти важные задачи] так, чтобы корректность была проверяема без меня.

    Примеры таких задач:

    • Все переходы статусов бронирования должны быть выразимы как типизированные переходы в конечном автомате, без raw string comparison

    • Невалидные комбинации полей заявки должны быть невыразимы в конструкторе доменного объекта

    • Все side effects при отмене должны быть явно перечислены в политике, а не размазаны по хендлерам

    • В этом легаси нечего тестировать, потому спроектируй швы по Физерсу, чтобы добавить датчики измерений, вот тебе список датчиков

    Или подробней: "В этом коде нет точек наблюдения. Невозможно проверить, что при отмене бронирования корректно начисляются штрафы, потому что логика размазана по шаблонам, сессии и триггерам. Спроектируй швы по Физерсу: выдели доменный объект, изолируй side effects, внедри датчики. Вот список наблюдений, которые должны быть возможны после этого. Пока швов нет — я не принимаю задачу, потому что проверить нечего"

    Ну или это должен быть тимлид/архитектор, который очень-очень хорошо знает QA

    Это компетенция. А на какой именно роли и тем более на каком человеке - по ситуации