Sangcho.log
dev

HyperFrames 탐구 3 — Golden MP4 회귀 테스트, 완성된 방의 기념사진

10 min read|

"영상을 뽑는 프레임워크"의 코드를 고쳤을 때, 그 수정이 기존 영상을 미세하게 망가뜨리지 않았다는 걸 어떻게 보장할까요? 화면 몇 장 눈으로 보는 걸로는 부족합니다. 660프레임 중 3프레임의 픽셀이 어긋나는 회귀는 코드 리뷰로 절대 못 잡으니까요. 이번 편은 HyperFrames의 답 — 완성된 .mp4 파일끼리 비교하는 Golden 테스트 — 그리고 후반부에 "감이 아니라 규칙으로" 굴러가는 스토리보드 시스템을 다룹니다.

누구를 위한 테스트인가 — 먼저 선을 긋자

이 테스트를 이해하는 첫걸음은 대상을 정확히 하는 것입니다.

구분 사용자 (비디오 제작자) 프레임워크 개발자 (Core/Engine)
작업 흐름 Storyboard → HTML 수정 → Preview → Render packages/ 코드 수정 → PR
자동 테스트 없음 (판정 불가) 있음 (자동 회귀 테스트)
판정 방식 연출 의도를 사람이 눈으로 판단 Golden과 새 출력을 기계가 픽셀 비교

사용자 쪽에 자동 합격/불합격이 없는 이유는 본질적입니다. 내 비디오가 "어떤 연출로 나와야 하는지"의 정답은 기계가 알 수 없고 제작자만 압니다. 그래서 사용자에겐 lint와 snapshot(눈 검사 보조)만 주어집니다.

반면 프레임워크는 다릅니다. HyperFrames를 **"HTML을 넣으면 비디오가 나오는 복사기"**라고 생각해 보세요. 복사기 부품(렌더링 엔진)을 교체했을 때 검사 기준은 명확합니다 — "같은 원본을 넣었을 때 어제와 오늘의 복사본이 동일한가?" 이건 기계가 판정할 수 있습니다.

검증 파이프라인 — 세 겹의 비교

저장소에는 시험용 원본(픽스처 HTML)과 정답 복사본(Golden .mp4)이 보관되어 있습니다. PR이 올라오면 CI가 픽스처를 새 코드로 렌더링해 후보 .mp4를 만들고, Golden과 세 가지 방법으로 대조합니다.

수정된 코드 → 후보 .mp4 렌더링
                  │
       ┌──────────┼──────────────┐
       ▼          ▼              ▼
  ① 컴파일     ② 시각 PSNR    ③ 오디오
  구조 비교     100지점 표본    상관계수 + 잔차
       └──────────┴──────────────┘
                  ▼
         전부 통과 → 머지 가능 / 하나라도 실패 → 차단

① 컴파일 구조 비교 — 후보 compiled.html의 DOM 구조 변경 여부를 텍스트 단위로 검증합니다.

② 시각 회귀 (PSNR) — 영상 전체를 100개 타임스탬프로 균등 분할하고, 각 지점에서 가장 가까운 프레임을 캡처해 FFmpeg로 두 이미지의 PSNR을 계산합니다. minPsnr 기준(예: 35dB)을 넘어야 프레임 합격, 실패 프레임 수가 maxFrameFailures 이하여야 테스트 통과입니다. 무손실이 필요한 png-sequence 포맷은 아예 바이트 단위 완전 일치를 요구합니다.

③ 오디오 검증 — 두 가지 방법을 겹쳐 씁니다.

  • 엔벨로프 상관관계: 2048샘플 윈도우로 RMS 엔벨로프를 구해 Pearson 상관계수를 계산, minAudioCorrelation(예: 0.995) 이상인지 확인
  • 잔차 신호 분석: Golden 오디오의 위상을 반전시켜 후보와 믹싱(AMIX)합니다. 두 신호가 같다면 완벽히 상쇄되어 무음이 되겠죠. 잔차가 −50dB 이하여야 합격 — 노이즈 캔슬링의 원리를 테스트에 쓴 겁니다.

허용 오차는 픽스처마다 meta.json으로 선언합니다.

{
  "name": "animejs-adapter-test",
  "minPsnr": 35.0,
  "maxFrameFailures": 0,
  "minAudioCorrelation": 0.995,
  "renderConfig": { "fps": 30 }
}

잠깐, PSNR이 뭐길래

PSNR(Peak Signal-to-Noise Ratio)은 원본 대비 얼마나 왜곡되었는가를 dB로 수치화한 대표적 화질 지표입니다. 픽셀 차이의 평균 제곱 오차(MSE)를 구한 뒤 10·log₁₀(MAX²/MSE)로 계산하고, 값이 높을수록 원본과 유사합니다. 8bit 이미지 기준으로 30dB 이상이면 양호, 40dB 이상이면 매우 우수, 50dB 이상이면 사실상 구분 불가이고, 두 이미지가 완전히 같으면 무한대(inf)가 나옵니다. 계산이 단순하고 객관적이라 코덱 성능 비교의 표준처럼 쓰이지만, 사람의 시각 인지와 항상 일치하지는 않아 최근엔 SSIM·LPIPS·VMAF 같은 지표를 함께 쓰는 추세입니다. HyperFrames의 용도 — "거의 동일해야 정상인 두 영상의 차이 감지" — 에는 단순한 PSNR이 딱 맞습니다.

Golden은 어디서 왔는가 — 기념사진의 탄생

여기서 방향을 헷갈리기 쉽습니다. "정답을 미리 걸어두고 코드가 거기에 맞춰 발전하는 건가?" — 정확히 반대입니다.

  • TDD — 과녁(정답)을 먼저 걸고 활(코드)을 쏘아 맞추는 과정
  • 회귀 테스트 — 방을 깨끗이 정리한 상태의 기념사진을 찍어두고, 나중에 청소(리팩토링)하다가 사진과 어긋나면 경보를 울리는 시스템

Golden은 외부에서 온 절대 기준이 아니라, 코드가 가장 완벽하다고 사람이 판단한 순간의 출력을 박제한 것입니다.

[기능 구현 완료] → [Docker에서 1회 렌더링] → [사람이 눈으로 "올바르다" 판정]
              → [Git에 커밋·동결] → [Golden 등극]

옳음의 판정은 사람이 최초 1회, 변하지 않음의 감시는 기계가 매 PR마다 영원히. CI는 Golden을 절대 스스로 만들거나 덮어쓰지 않고 읽기만 합니다. Golden이 갱신되는 시점은 딱 둘 — 픽스처를 처음 추가할 때, 그리고 의도적으로 출력을 바꿀 때(예: 오디오 페이드 곡선 개선)입니다. 후자는 같은 PR에서 docker:test:update로 새 Golden을 함께 커밋하고 리뷰어가 승인합니다.

왜 반드시 Docker인가 — Pixel Drift

macOS와 Linux, 서로 다른 Chrome/FFmpeg 버전 사이에는 텍스트 안티앨리어싱과 셰이더 연산의 미세한 차이로 픽셀 드리프트가 생깁니다. 개발자 맥북에서 뽑은 Golden을 리눅스 CI에서 비교하면, 코드가 멀쩡한데도 PSNR 검사가 우수수 실패합니다. 그래서 규칙은 단호합니다 — Golden의 생성과 검증은 고정된 Chromium 버전과 시스템 폰트가 설치된 전용 Docker 컨테이너 안에서만. (이 차이의 기술적 뿌리인 screenshot/beginframe 캡처 모드는 4편에서 다룹니다.)

같은 프레임을 다루더라도 목적별로 연산량이 완전히 다르다는 것도 정리해 둘 만합니다.

활동 연산 범위 (20초·30fps 기준) 판정 주체 목적
제작 (Render) 600장 전수 없음 배포용 .mp4 생성
검증 (Regression) 100개 표본 지점 기계 (Golden 대조) 회귀·픽셀 오차 방지
스냅샷 (Snapshot) 5장 내외 (비트 중간) 사람/에이전트의 눈 연출·레이아웃 육안 검사

후반부: 시각 요소는 감이 아니라 규칙으로 — STORYBOARD 시스템

테스트가 "만든 것을 지키는" 장치라면, 스토리보드는 "만들 것을 통제하는" 장치입니다. HyperFrames에서 시각 연출은 5겹 필터를 통과해야 합니다 — ① DESIGN.md(브랜드 HEX·폰트 외 배제) → ② SCRIPT.md(비트 길이는 발화 속도에 종속) → ③ Asset Audit(캡처된 자산 목록 사전 감사) → ④ 리듬 템플릿 → ⑤ 비트별 연출 매핑.

그 위에 하드 룰이 얹힙니다.

  • 제품 스크린샷·히어로 이미지가 전체 시간의 50% 이상 노출
  • 로고는 **첫 비트와 마지막 비트(CTA)**에 반드시 등장
  • 텍스트만으로 채워진 비트는 연속 2개까지, 첫 비트는 텍스트 전용 금지
  • 스냅샷에서 화면의 40% 이상이 빈 배경이면 불합격 (Sparse 검증)

모니터용 웹과 TV/모바일용 영상은 적정 스케일 자체가 다르다는 변환표도 흥미롭습니다.

요소 일반 웹 비디오 어댑션 이유
Headline 32–48px 64–120px 시청 거리 가독성
Body 14–16px 28–42px 모바일 시청
장식 요소 투명도 3–8% 12–25% 낮으면 영상에서 안 보임
Border 1px 2–4px 압축 열화 대비
Padding 16–32px 60–140px TV Safe Area

연출 언어까지 표준화되어 있습니다. 모션은 "움직인다"가 아니라 에너지 등급이 있는 동사로 지시하고(SLAMS/CASCADE/FLOATS), 트랜지션은 매트릭스로 고릅니다 — WebGL 셰이더 전환은 핵심 임팩트 지점에 비디오당 1~2회만, CSS 전환은 안정적 연결부에, 하드 컷은 빠른 템포 구간에. 애니메이션 타이밍도 규칙입니다: 들어올 땐 강하고 빠르게(0.25s), 나갈 땐 부드럽게, 모든 요소의 동시 시작은 금지하고 0.1~0.3초씩 엇갈리게(stagger).

왜 이렇게까지 규칙화할까요? 2편의 원칙과 정확히 이어집니다 — 저자가 LLM이기 때문입니다. 사람 디자이너의 감각을 기대할 수 없으니, 그 감각을 명시적 규칙과 기계 검증(Sparse 40% 등)으로 번역해 둔 것입니다.

더 깊이: Golden 테스트의 계보 — 스냅샷 테스팅과 그 함정

"출력을 박제해 두고 변화를 감지한다"는 기법 자체는 오래된 전통입니다. Jest의 스냅샷 테스트(컴포넌트 출력 직렬화 비교), Percy·Chromatic 같은 시각 회귀 서비스(스크린샷 diff), 컴파일러 개발의 golden file 테스트가 모두 같은 과입니다. HyperFrames는 이걸 "완성된 mp4 + 지각 지표(PSNR)"까지 확장한 사례고요.

이 계열의 테스트에는 공통 함정이 있고, HyperFrames의 설계는 그에 대한 답으로 읽을 수 있습니다.

  1. "무엇이 옳은가"를 테스트가 모른다 — 스냅샷은 처음 커밋된 출력이 옳다고 가정할 뿐입니다. 그래서 HyperFrames는 동결 전 "사람이 눈으로 판정"하는 단계를 명시하고, 갱신도 리뷰어 승인을 요구합니다.
  2. 무분별한 갱신(snapshot blindness) — 테스트가 깨질 때마다 생각 없이 --update를 치면 안전망이 무력화됩니다. Golden 갱신을 "의도적 출력 변경 PR"로 한정한 것이 그 방어입니다.
  3. 환경 민감성 — 시각 스냅샷은 렌더링 환경에 극도로 민감합니다. Docker 고정 환경과 PSNR 허용 오차(바이트 일치가 아니라 35dB 기준)가 이 문제에 대한 이중 완충입니다.

요약하면, golden 테스트는 "정확성 검증"이 아니라 "변화 감지" 도구입니다. 이 구분을 유지하는 팀에게는 리팩토링의 든든한 안전망이 되고, 흐리는 팀에게는 형식적인 초록불이 됩니다.


한 줄 요약 — HyperFrames의 Producer 테스트는 사람이 한 번 승인한 출력(Golden MP4)을 Docker 고정 환경에서 박제해 두고, 이후 모든 코드 변경이 그 출력을 PSNR·오디오 상관계수 기준으로 재현하는지 기계가 감시하는 "기념사진" 시스템이다.

스스로 점검할 질문

  1. TDD와 회귀 테스트의 방향 차이를 "과녁"과 "기념사진" 비유로 설명할 수 있나요?
  2. Golden을 macOS에서 생성하면 안 되는 이유는 무엇이고, 여러분의 시각 회귀 테스트에는 같은 문제가 없나요?
  3. 오디오 비교에 상관계수와 위상 반전 잔차, 두 가지를 겹쳐 쓰는 이유는 무엇일까요?

참고 링크