Регрессионное тестирование и золотые эталоны

Автор: Николай СапуновОбновлено: август 202621 мин чтения
Содержание статьи +

Кратко

Регрессионный тест качества видео перекодирует фиксированный набор эталонных клипов на каждом изменении и сравнивает новые оценки качества с сохранённым базовым уровнем – золотым эталоном, – чтобы поймать сборку, которая тихо снизила качество, до того как она уйдёт в прод. Идея заимствована прямо из тестирования ПО на золотых мастерах (golden master / характеризационные тесты), но с одной поправкой, которая меняет всё: оценка вроде VMAF – не точное число, поэтому сравнивают с полосой допуска размером с шум метрики, а не с точной линией. Один клип – не набор: набор эталонов должен покрывать контент, который обманывает метрики (зерно, тёмные сцены, высокую динамику, экранный текст, анимацию), иначе тест пройдёт, не заметив регрессию, что живёт в пробеле. Самую трудную регрессию даёт медленный дрейф: серия сборок, каждая теряющая долю балла, каждый шаг внутри полосы шума, что складывается в видимую потерю только при измерении от замороженного эталона, а не от прошлой сборки.

Зачем это нужно

Конвейер кодирования не строят один раз и не оставляют в покое – он получает новую версию кодека, настроенный пресет, отрефакторенную цепочку фильтров, апгрейд зависимости, и любой из них может снизить качество картинки, которое видят зрители, без единой ошибки в логе. Регрессионный тест – это постоянная страховочная сетка, что ловит такое: фиксированная библиотека клипов, сохранённый набор известных-хороших оценок и автоматическое сравнение на каждом изменении. Эта статья – для лида кодирования, платформенного инженера и QA-инженера, которым нужно построить эту сетку и доверять ей: знать, какие клипы в неё положить, с чем сравнивать и как отличить реальную регрессию от джиттера метрики. Это набор тестов, что стоит за гейтом качества в CI/CD: гейт – это растяжка, которая валит одну сборку; регрессионный набор – это курируемая библиотека эталонов и базовые оценки, которые делают растяжку осмысленной на протяжении месяцев и сотен сборок.

Золотой эталон, заимствованный из тестирования ПО

Термин пришёл из разработки, а не из видео, и его родословную стоит знать, потому что она точно говорит, для чего нужен золотой эталон. Когда у разработчика есть код, чьё поведение он хочет защитить при рефакторинге, он может написать характеризационный тест – тест, который записывает, что код делает сейчас, и валится, если позднее изменение это поведение поменяло. Технику назвал Майкл Физерс в книге Working Effectively with Legacy Code (2004), и у неё есть два других имени, которые встретятся в том же ряду: golden master testing и approval testing. Записанный вывод – снимок, с которым тест сравнивает, – это золотой мастер, или золотой эталон (Feathers, 2004; Characterization test, Wikipedia, 2026).

Механика проста. На первом запуске тест захватывает вывод и сохраняет его как базовый уровень; этот захват «благословляют» как правильный. Каждый следующий запуск перевыполняет код и сравнивает новый вывод с сохранённым. Совпало – тест прошёл; отличается – тест провалился, и человек смотрит на диф, чтобы решить, было ли изменение намеренным (Anderson, Golden Master Regression, 2017). В «режиме верификации», как говорит один практик, сохранённый снимок «работает как регрессионный тест» – он не знает, как выглядит правильно, только то, как выглядит неизменно.

Регрессионный тест качества видео – та же машина, нацеленная на картинку. Золотой эталон – это фиксированный, версионируемый исходный клип (а полезнее – набор клипов) вместе с оценками качества, которые произвела на нём известная-хорошая сборка. На каждом изменении пути кодирования конвейер перекодирует эти самые клипы новым кодом, измеряет качество и сравнивает новые оценки с сохранённым базовым уровнем. Если качество упало за пределы допуска, тест проваливается. Эталон – это не мера хорошо; это мера неизменно. Это различие – весь смысл техники, и именно оно отделяет регрессионный тест от абсолютного пола в гейте качества: пол спрашивает «достаточно ли хорош этот энкод?»; регрессионный тест спрашивает «хуже ли этот энкод того, которому мы уже доверяли?».

Рис. 1. Одна машина, два мира. Golden-master тест в ПО записывает снимок вывода программы и валится, когда поздний прогон отличается; видео-регресс-тест записывает оценки качества известных-хороших энкодов фиксированных эталонных клипов и валится, когда новая сборка набирает хуже. Золотой эталон измеряет «неизменно», а не «правильно».

Что такое золотой эталонный клип на самом деле

Золотой эталон в видео состоит из двух частей, и команды, что пропускают вторую, получают тест, который дрейфует. Первая часть – исходный мастер: фиксированный, чистый клип, хранимый под версионным контролем или в закреплённом объектном хранилище, который никогда не меняется. Он должен быть высокого качества с запасом – если ваш эталонный источник уже посредственный файл на 4 Мбит/с, любой энкод его набирает мало, и тест не может отличить хорошую сборку от плохой. Эталонные источники выбирают так, чтобы хороший энкод попадал в верхнюю часть шкалы (VMAF примерно 92–95 на верхней ступени), где у теста есть место увидеть падение (практика кодирования; рекомендации по выбору эталонных клипов, 2024).

Вторая часть – базовые оценки: пер-клиповые, пер-rendition, покадровые числа качества, которые текущая прод-сборка производит на этом источнике, записанные и закоммиченные рядом с ним. Это и есть собственно золотой мастер – снимок, с которым сверяется каждая будущая сборка. Сохранить одно заголовочное число мало; сохраняйте покадровую кривую, потому что когда регрессия сработает, вам понадобится увидеть, где в клипе качество упало, а не только что упало (чтение худших кадров объясняет, почему распределение важнее среднего).

Три вещи нужно закрепить (pin), иначе базовый уровень тихо перебазируется сам, и тест становится флакающим по причинам, не связанным с кодировщиком. Закрепите исходный клип (повторная загрузка с публичного URL, который меняется, – это уже другой эталон). Закрепите файл модели VMAF как lockfile: default-, phone- и 4K-модели дают разные оценки, поэтому незакреплённая модель сдвигает каждый базовый уровень (VMAF в деталях разбирает выбор модели). Закрепите версии инструментов: синтаксис опций фильтра libvmaf менялся между релизами FFmpeg, и плавающий инструмент может сдвинуть число. Это видео-эквивалент того, что практики approval testing называют скраббером (scrubber): часть golden-master-харнесса, которая убирает недетерминированный шум – таймстемпы, случайные ID – перед сравнением, чтобы тест реагировал только на реальные изменения (практика approval testing, octopusinvitro, 2021). Скраббить саму оценку VMAF нельзя, поэтому вместо этого закрепляют всё, что её питает, и поглощают остаточный шум полосой допуска – о ней следующий раздел.

Порог – это полоса, а не линия

Здесь видео расходится с учебниковым golden master в ПО, и ошибка тут – самый частый способ провалить такой набор. Классический golden-master тест делает точное сравнение: байт в байт, новый вывод либо совпадает со снимком, либо нет. Это работает, когда вывод детерминирован. Оценка качества видео недетерминирована. Прогоните один и тот же энкод дважды, и VMAF может сдвинуться на несколько десятых, потому что большинство кодировщиков не битово-точны от прогона к прогону – многопоточность чуть меняет вывод. Поверх этого VMAF сам – это предсказание модели, обученной на оценках людей, поэтому он несёт доверительный интервал: bootstrap-модели Netflix (например vmaf_b_v0.6.3) сообщают стандартное отклонение напрямую, где 95%-интервал – это примерно оценка плюс-минус 1,96 стандартного отклонения (Netflix, документация VMAF Confidence Interval, начиная с v1.3.7, 2018).

Поэтому точное сравнение – неверный инструмент. Если вы утверждаете «новая оценка должна равняться 93,4», тест провалится на первом же перекоде, что вернёт 93,3, а тест, который кричит «волки» на шуме, заглушат за неделю. Это известный паттерн далеко за пределами видео: недетерминированные системы тестируют допусками и порогами, а не точными совпадениями – допустимыми числовыми диапазонами или операторами приближённого равенства с заданным допуском (практика тестирования недетерминированных систем, 2025). Золотой мастер для видео-метрики поэтому – это полоса: базовая оценка плюс допустимое падение.

Правильную ширину этой полосы задаёт собственный шум метрики, и она выводится из арифметики, а не из догадки. Сложите стандартное отклонение модели и run-to-run отклонение кодировщика в квадратуре, затем масштабируйте до 95%:

combined_sigma = sqrt(model_sigma^2 + run_to_run_sigma^2)
               = sqrt(0.5^2 + 0.3^2)
               = sqrt(0.34)
               = 0.58 VMAF

полоса допуска = 1.96 x 0.58 = 1.14 VMAF

Падение в пределах примерно 1,1 VMAF на этом клипе неотличимо от шума и должно проходить; падение сверх – реальное изменение, достойное внимания человека. (Статья про гейт качества проходит ту же квадратуру для одиночного гейта; регрессионный набор применяет её по каждому клипу, потому что шум зависит от контента – динамичный клип дрожит сильнее статичного.) Каждый эталонный клип и каждый rendition поэтому получают свою полосу, измеренную один раз при благословлении базового уровня:

Эталонный клипНагрузка на контентБаза VMAF (1080p)Изм. полоса шумаПровал, если падение >
grain_fieldЗерно плёнки, текстура94.1±1.21.2 VMAF
night_driveТёмное, низкий свет92.8±1.51.5 VMAF
sports_panВысокая динамика91.6±1.61.6 VMAF
screen_shareТекст, резкие края95.3±0.90.9 VMAF
animation_2dПлоский цвет, градиенты96.0±0.70.7 VMAF

Таблица 1. Каждый золотой эталон несёт свою базу и свою полосу размером с шум. Шумный контент (тёмный, динамичный) получает полосу шире; чистый (анимация, экранный текст) – уже. Сборка валит клип, когда её падение от базы превышает полосу клипа. Числа иллюстративны; измеряйте своё по клипу и кодеку.

Рис. 2. Почему сравнение – полоса, а не линия. Линия точного совпадения (слева) валится на каждом перекоде, что дрогнул на десятую долю. Полоса допуска (справа), размером с совмещённый шум модели и кодировщика, пропускает джиттер и валит только падение, что пробивает шум, – реальную регрессию.

Один клип – не набор: собираем набор эталонов

Регрессионный набор с одним клипом тестирует один вид контента и слеп к остальным. Опасность конкретна: у объективных метрик есть контент-зависимые слепые пятна, поэтому регрессия, что проявляется только на, скажем, тёмном градиенте, проскочит мимо набора, собранного из яркого клипа «говорящая голова». Задача набора эталонов – покрыть контент, что ломает кодировщики, и контент, что обманывает метрики, чтобы что бы изменение ни деградировало, какой-то клип в наборе это почувствовал.

Рис. 3. Набор эталонов как карта покрытия. Каждый клип заслуживает место, нагружая свою слабость кодировщика и закрывая своё слепое пятно метрики, – так ни один режим отказа не остаётся непротестированным.

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

Тип контентаЧто нагружает в кодировщикеСлепое пятно метрики, что закрываетСсылка
Зерно / текстураСохранение детали против шумодаваPSNR награждает сглаживание, убирающее зерногде врут метрики
Тёмное / низкий светБандинг в почти-чёрных градиентахVMAF (до v1) слаб на бандингебандинг
Высокая динамика / спортТемпоральная обработка, дропы кадровПокадровый пулинг прячет джаддерджаддер
Экранный контент / текстРезкие края, рингинг, цветностьМетрики по luma не видят растекание цветацветовые артефакты
Анимация / плоский цветБандинг градиентов, контурингГладкие зоны маскируют локальные сбоибандинг

Таблица 2. Набор эталонов охватывает контент, что ломает кодировщики, и контент, что обманывает метрики. Каждый клип выбран потому, что какая-то метрика на нём слаба, – поэтому у набора в целом нет слепого пятна, что оставил бы один клип.

Здесь же регрессионный набор оправдывает себя сверх одиночного гейта: держа постоянную библиотеку типов контента, которые есть в вашем реальном каталоге, он превращает «энкод выглядит нормально на моём тестовом клипе» в «энкод удержал качество на зерне, тёмном, движении, тексте и анимации». Курирование этой библиотеки – разовая инвестиция, которая окупается на каждой сборке весь срок жизни конвейера. Причинно-следственные основания того, почему каждый тип контента труден, – битовая глубина и зерно, обработка частоты кадров, субдискретизация цветности – живут в разделе Video Encoding; задача набора – измерить результат, а не выводить механизм заново.

Регрессия, которую не валит ни одна сборка: ловим медленный дрейф

Самую опасную регрессию не виновата ни одна отдельная сборка. Представьте базу на VMAF 96,0 и полосу допуска 1,1. Сборка за сборкой качество сползает: 96,0 → 95,6 → 95,2 → 94,8 → 94,4 → 94,0. Каждый отдельный шаг – это падение на 0,4 VMAF – удобно внутри полосы 1,1 – поэтому проверка, что сравнивает каждую сборку лишь с последней хорошей, проходит каждую из них, всегда. Шесть сборок спустя качество упало на 2,0 VMAF от старта, треть едва-заметной разницы (1 JND ≈ 6 VMAF; Ozer, 2017) и движется к видимому, а проверка «против последней» не показала ни одной красной сборки.

Против последней сборки (каждый шаг к предыдущей):
  96.0 -> 95.6   шаг 0.4  < 1.1  PASS
  95.6 -> 95.2   шаг 0.4  < 1.1  PASS
  95.2 -> 94.8   шаг 0.4  < 1.1  PASS
  94.8 -> 94.4   шаг 0.4  < 1.1  PASS
  94.4 -> 94.0   шаг 0.4  < 1.1  PASS   <- не срабатывает

Против замороженного эталона (каждая сборка к исходным 96.0):
  сборка 3: 95.2  накопл. 0.8  < 1.1  PASS
  сборка 4: 94.8  накопл. 1.2  > 1.1  FAIL  <- поймано здесь
  сборка 6: 94.0  накопл. 2.0  > 1.1  (уже валится)

Лечение – это дисциплина, а не более хитрый порог: всегда сравнивайте с замороженным золотым эталоном, а не только с предыдущей сборкой. Замороженный эталон – это снимок, благословлённый при создании набора (или при последнем намеренном перебазировании); он не двигается оттого, что сборка его толкнула. Сравнение каждой сборки с этой фиксированной точкой превращает медленное сползание в накопленное число, что пробивает полосу в тот момент, когда суммарная потеря её превышает, – здесь на сборке четыре, первой сборке, чьё накопленное падение (1,2) бьёт полосу 1,1, хотя её собственный шаг был крошечные 0,4. В паре с трендовым видом – нанося оценку каждой сборки против замороженного эталона во времени, как контрольная карта следит за процессом, – это позволяет человеку увидеть наклон задолго до того, как он пересечёт линию.

Рис. 4. Медленный дрейф побеждает проверку «против последней сборки». Каждый шаг (0,4 VMAF) сидит внутри полосы шума, поэтому проверка от сборки к сборке проходит все шесть. Измеренная от замороженного эталона, накопленная потеря пробивает полосу на сборке четыре – регрессия, которую набор должен поймать.

Перебазирование без благословления регрессии

Замороженный эталон – смысл техники, но базовые уровни иногда всё же приходится двигать, и то, как вы их двигаете, – это место, где регрессионный набор либо тихо компрометируют, либо держат честным. Есть ровно две законные причины перебагословить золотой мастер. Первая – намеренное, проверенное улучшение качества: лучший кодировщик действительно поднимает VMAF, вы подтверждаете это на покадровых кривых и в идеале быстрой субъективной проверкой и коммитите более высокие оценки как новый базовый уровень. Вторая – изменение измерения, которое вы решили принять: важнее всего апгрейд модели VMAF – например, до VMAF v1 (июнь 2026), которая добавляет осведомлённость о цветности и бандинге, чего не было у старых моделей (Netflix Technology Blog, VMAF v1, 2026), – что сдвигает каждую оценку и заставляет чисто перемерить каждый базовый уровень в том же изменении.

Режим отказа, от которого надо отказаться, – перебазирование на красном: сборка приходит низкой, и кто-то «чинит» провалившийся тест, благословляя новые, более низкие оценки как базовый уровень. Это не чинит регрессию; это отмывает её в новую норму, и следующее сползание стартует уже отсюда. Защита – процесс: перебазирование – явное, отрецензированное, одобренное человеком действие, а не автоматическое «обновить базу при провале». Золотой мастер одобряют так же, как pull request, – человеком, который посмотрел на диф и покадровый график и решил, что изменение намеренно. Зашейте это в инструмент: набор должен отказываться перезаписывать базовый уровень без явного флага одобрения, чтобы красная сборка никогда не стала тихо новой зелёной.

Когда меняется и битрейт: сравнивайте на кривой

Один случай ломает регресс-проверку при фиксированном битрейте. Если изменение меняет и битрейт, и качество – новый кодек тратит меньше бит, скажем, – сравнение VMAF в одной рабочей точке несправедливо к тому, кто тратит меньше. Честное сравнение держит качество постоянным и спрашивает про биты: BD-rate (Bjontegaard Delta rate) сообщает среднюю разницу битрейта между двумя энкодами при равном качестве, где отрицательное число значит равное качество за меньше бит (Bjontegaard, VCEG-M33, 2001). Для апгрейда кодека или пресета в наборе прогоните каждый эталонный клип на нескольких битрейтах, постройте кривую качество-битрейт и регрессионно тестируйте BD-rate против базовой кривой – так настоящий выигрыш в эффективности проходит, хотя пер-файловый VMAF сдвинулся, а сборка, что требует больше бит на то же качество, валится. Держите BD-rate отдельно от оценки качества: это экономия при равном качестве, а не число качества. Наши собственные метод и цифры BD-rate – в BD-rate на наших цифрах.

Частая ошибка: тест против «последней зелёной» вместо замороженного эталона

Две ошибки, что выхолащивают регрессионный набор, – зеркальные, и обе из забывания того, что такое золотой мастер. Первая – сравнение с последней сборкой, ловушка дрейфа из раздела выше: всегда меряя против самой свежей зелёной сборки, набор позволяет качеству эродировать по одному под-пороговому шагу и не срабатывает никогда. Лечение – замороженный эталон плюс трендовый вид. Вторая – перебазирование при провале: сделать красный тест зелёным, приняв более низкие оценки, что благословляет регрессию вместо её поимки. Лечение – явное, одобренное человеком перебазирование, что может случиться только намеренно. Обе ошибки делят корень – золотой эталон защищает вас, только пока остаётся фиксированным и двигается лишь намеренно. Эталон истины за всем этим – по-прежнему правильно проведённый субъективный тест (ITU-R BT.500-15, 2023): когда набор и внимательный просмотр расходятся, побеждает просмотр, и набор перекалибруют под глаз, а не наоборот.

Где здесь Фора Софт

Фора Софт строит системы видеостриминга, OTT, конференц-связи, e-learning, видеонаблюдения и телемедицины с 2005 года, и на тех, у кого есть собственный конвейер кодирования, регрессионный набор – это то, что не даёт рутинному апгрейду зависимости или кодека тихо подъедать картинку за квартал. Мы помогаем командам поднять набор, что описан в этой статье, – курируемую библиотеку эталонов, что охватывает их реальные типы контента, замороженные базовые уровни с пер-клиповыми полосами шума, проверку дрейфа против замороженного эталона вместо «против последней сборки» и шаг перебазирования, который человек должен одобрить намеренно. Где проекту нужны данные качество-битрейт за базовым уровнем для конкретного кодека и типа контента, наши измеренные бенчмарки (см. нашу методологию бенчмарков) дают стартовые числа. Цель – набор, что молчит месяцами, а затем краснеет ровно на той сборке, что это заслужила.

Ключевые выводы

  • Регресс-тест перекодирует фиксированные эталонные клипы и сравнивает оценки с сохранённой золотой базой.
  • Золотой эталон измеряет «неизменно», а не «хорошо» – заимствовано из golden-master тестирования в ПО.
  • Сравнивайте в пределах полосы допуска размером с шум метрики, а не с точной линией.
  • Один клип – не набор: охватите зерно, тёмное, движение, текст и анимацию, чтобы закрыть слепые пятна.
  • Ловите медленный дрейф, сравнивая каждую сборку с замороженным эталоном, а не с последней.
  • Перебазируйте только намеренным, одобренным человеком действием – никогда не авто-обновляйте провалившийся тест.

Что почитать дальше

Строите такую систему?

Подберём параметры кодирования под ваш контент и посчитаем стоимость доставки до старта разработки.