Menguji tool Command Line Interface (CLI) yang memproses atau menghasilkan aset multimedia seperti video sering kali menimbulkan masalah pada pipeline CI/CD: eksekusi lambat, konsumsi resource tinggi, dan flaky test akibat rendering non-deterministik. Perbedaan mikroarsitektur CPU, optimasi encoder lossy, serta variasi floating-point pada headless browser dapat menghasilkan hash biner file yang berbeda meskipun input sama.
Untuk mengatasi masalah ini, pipeline uji regresi harus memisahkan logika orkestrasi CLI dari proses rendering video yang sebenarnya. Pendekatan deterministik berfokus pada verifikasi parameter pemanggilan, struktur container, dan metadata stream tanpa menjalankan proses encoding penuh.
Masalah Non-Determinisme pada Pipeline Multimedia
Pengujian regresi multimedia berbasis perbandingan biner (seperti membandingkan SHA-256 dari MP4 output) hampir selalu gagal. Penyebab utamanya meliputi:
- Header Metadata Waktu: Container MP4 menyimpan timestamp pembuatan (
creation_time) di dalam atommvhd. Dua eksekusi berturut-turut akan menghasilkan hash biner yang berbeda. - Perilaku Floating-Point GPU/CPU: Canvas rendering di browser headless (Chromium/Puppeteer) menghasilkan perbedaan rasterisasi piksel sub-pixel antarmesin.
- Lossy Codec Rate Control: Algoritma libx264/libvpx mengoptimalkan bitrate berdasarkan alokasi dinamis yang tidak selalu byte-identical lintas arsitektur sistem operasi.
Arsitektur Deterministic Mocking FFmpeg
Solusi tercepat dan paling stabil untuk level unit dan integrasi CLI adalah mengisolasi eksekusi binari eksternal. Jangan biarkan pipeline uji memanggil binari ffmpeg asli dari sistem host untuk seluruh test suite.
Gunakan pola wrapper process execution. CLI Anda harus mengeksekusi FFmpeg melalui interface yang dapat disubstitusi dengan mock script atau runner tiruan pada level subproses.
// internal/runner/exec.go
package runner
import (
"context"
"os/exec"
)
type CommandRunner interface {
Run(ctx context.Context, name string, args ...string) ([]byte, error)
}
type RealRunner struct{}
func (r *RealRunner) Run(ctx context.Context, name string, args ...string) ([]byte, error) {
cmd := exec.CommandContext(ctx, name, args...)
return cmd.CombinedOutput()
}
Dalam pengujian integrasi CLI, gantikan RealRunner dengan stub yang memvalidasi bendera (flags) CLI yang diteruskan ke FFmpeg tanpa memproses frame:
// internal/runner/mock_test.go
package runner_test
import (
"context"
"testing"
)
type MockFFmpegRunner struct {
LastExecutedArgs []string
}
func (m *MockFFmpegRunner) Run(ctx context.Context, name string, args ...string) ([]byte, error) {
m.LastExecutedArgs = args
// Kembalikan simulasi output stdout FFmpeg jika diperlukan oleh parser CLI
return []byte("frame= 100 fps=0.0 q=-1.0 Lsize= 512kB time=00:00:04.00 bitrate=1048.6kbits/s"), nil
}
func TestCLIArgumentConstruction(t *testing.T) {
mock := &MockFFmpegRunner{}
// Injeksi mock ke dalam instance CLI video generator
app := NewVideoCLI(mock)
err := app.Export("input.json", "output.mp4")
if err != nil {
t.Fatalf("eksekusi CLI gagal: %v", err)
}
// Verifikasi argumen deterministik penting
expectedFlag := "-c:v"
expectedCodec := "libx264"
found := false
for i, arg := range mock.LastExecutedArgs {
if arg == expectedFlag && i+1 < len(mock.LastExecutedArgs) && mock.LastExecutedArgs[i+1] == expectedCodec {
found = true
break
}
}
if !found {
t.Errorf("flag encoding tidak sesuai, didapat: %v", mock.LastExecutedArgs)
}
}
Isolasi Headless Browser untuk CLI Generator Frame
Jika CLI merender video berbasis frame web (HTML/Canvas to video), pastikan konfigurasi browser headless diatur secara ketat agar menghasilkan output konsisten:
- Nonaktifkan subpixel font scaling dan smoothing yang bergantung pada GPU OS dengan argumen:
--disable-gpu,--disable-lcd-text, dan--force-color-profile=srgb. - Kunci viewport ke ukuran statis tanpa scrollbar:
--window-size=1920,1080. - Gunakan Static Virtual Clock: Hindari pemanggilan
Date.now()ataurequestAnimationFramebebas di script rendering. Kendalikan tick waktu secara manual dari proses controller via CDP (Chrome DevTools Protocol).
Verifikasi Snapshot Metadata File Menggunakan FFprobe
Untuk pengujian end-to-end (E2E) smoke test yang tetap memerlukan pembuatan file biner asli, buat file dummy seminimal mungkin (misal: 1 frame, durasi 0.1 detik). Jangan verifikasi file dengan perbandingan byte mentah atau decoding visual penuh.
Gunakan ffprobe terstruktur untuk memvalidasi atribut teknis video via JSON output:
ffprobe -v quiet -print_format json -show_format -show_streams dummy_output.mp4Parsing output tersebut dalam test assertion untuk memvalidasi invariants stream:
// metadata_test.go
package test
import (
"encoding/json"
"os/exec"
"testing"
)
type FFprobeOutput struct {
Streams []struct {
CodecName string `json:"codec_name"`
Width int `json:"width"`
Height int `json:"height"`
Duration string `json:"duration"`
RFrameRate string `json:"r_frame_rate"`
} `json:"streams"`
}
func TestVideoOutputStreamInvariants(t *testing.T) {
out, err := exec.Command("ffprobe", "-v", "quiet", "-print_format", "json", "-show_streams", "output.mp4").Output()
if err != nil {
t.Fatalf("gagal menjalankan ffprobe: %v", err)
}
var probe FFprobeOutput
if err := json.Unmarshal(out, &probe); err != nil {
t.Fatalf("gagal parsing output ffprobe: %v", err)
}
if len(probe.Streams) == 0 {
t.Fatal("tidak ada stream video ditemukan")
}
videoStream := probe.Streams[0]
if videoStream.CodecName != "h264" {
t.Errorf("codec mismatch: expected h264, got %s", videoStream.CodecName)
}
if videoStream.Width != 1920 || videoStream.Height != 1080 {
t.Errorf("dimensi resolusi tidak valid: %dx%d", videoStream.Width, videoStream.Height)
}
}
Isolasi Fixture Media dan Strategi Assertion CI/CD
Pengujian regresi multimedia di CI/CD harus menghemat bandwidth dan disk I/O. Terapkan aturan berikut pada repository Anda:
- Synthetic Colorbars Fixture: Alih-alih menyimpan sample MP4 berukuran puluhan megabyte di Git, buat fixture dinamis sebelum suite berjalan via FFmpeg filter
testsrc:
Perintah ini menghasilkan video 1 detik berukuran beberapa kilobyte secara instan tanpa ketergantungan file biner eksternal.ffmpeg -f lavfi -i testsrc=duration=1:size=320x240:rate=30 fixture.mp4 - Statiskan Timestamps: Jika Anda harus memeriksa kemiripan hash container, paksa container FFmpeg menulis muxing timestamp 0 dengan parameter:
-metadata creation_time="1970-01-01 00:00:00" -bitexact. - Metrik Perceptual Frame Sampling: Jika integritas frame harus diuji secara visual, hindari SSIM/PSNR pada ribuan frame. Ambil tepat 1 keyframe (I-frame) pada posisi offset deterministik, lalu bandingkan nilai Perceptual Hash (pHash) dengan toleransi Hamming Distance < 3.
Catatan Praktis: Pisahkan test suites ke dalam dua kategori. Jalankan unit test dengan FFmpeg Command Mocking di setiap commit atau pull request (< 5 detik). Jalankan smoke test Synthetic Fixture Metadata hanya pada tahap staging atau rilis tag untuk menjaga feedback loop developer tetap cepat.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!