Perbaikan hari ini tidak boleh melanggar hal yang sudah benar kemarin. Padahal di pipeline data, satu baris yang diubah — pembulatan, urutan sort, definisi kolom — menjalar diam-diam ke seluruh laporan turunannya, dan pemakai laporan yang paling dulu mengetahuinya. Regression testing menutup celah itu dengan cara paling sederhana: simpan output yang sudah disetujui sebagai berkas golden, lalu bandingkan output saat ini terhadapnya pada setiap perubahan kode. Test suite yang tadinya hanya menguji “apakah benar” kini juga menguji “apakah tetap sama”.
Golden File sebagai Kunci Perilaku #
| Aspek | Banding isi (CSV/JSON) | Banding hash |
|---|---|---|
| Yang terbaca saat merah | field, baris, nilai lama menjadi baru | hanya “berbeda” |
| Tempat di repositori | berkas datar yang mudah ditinjau | satu baris teks |
| Cocok untuk | tabel ringkas hasil agregat | berkas parquet besar |
Untuk tabel agregat yang ringkas, banding isi memberi reviewer diff yang bisa dibaca; untuk berkas parquet besar, cukup simpan hash SHA-256 di repositori dan bandingkan hash-nya — hash yang sama membuktikan isi sama tanpa membuka berkasnya.
Objek yang dijaga adalah hitung_ringkasan — agregat penjualan TokoKita per kota, penerapan langsung pola transformasi pada chapter Data Transformation Testing:
"""Transformasi ringkasan penjualan per kota — objek yang dijaga regression test."""
import numpy as np
import pandas as pd
def buat_df_pesanan(seed: int = 42, n: int = 500) -> pd.DataFrame:
"""Data uji deterministik: seed tetap menghasilkan baris yang sama."""
rng = np.random.default_rng(seed)
return pd.DataFrame({
"kota": rng.choice(["Jakarta", "Bandung", "Surabaya", "Medan"], n),
"total": rng.integers(25000, 500000, n),
})
def hitung_ringkasan(df: pd.DataFrame) -> pd.DataFrame:
"""Agregat per kota: jumlah pesanan, total, dan rata-rata 2 desimal."""
ringkas = df.groupby("kota", as_index=False).agg(
jumlah_pesanan=("total", "size"),
total=("total", "sum"))
ringkas["rata_rata"] = (ringkas["total"] / ringkas["jumlah_pesanan"]).round(2)
return ringkas.sort_values("kota").reset_index(drop=True)Test berikut membaca golden, membangun ulang output dari data ber-seed, lalu membandingkan keduanya kolom per kolom:
"""Regression test: output transform dibandingkan berkas golden."""
import os
from pathlib import Path
import pandas as pd
from transform import buat_df_pesanan, hitung_ringkasan
GOLDEN = Path("golden/ringkasan.csv")
def banding_dengan_golden(sekarang: pd.DataFrame, golden: pd.DataFrame) -> list[str]:
"""Banding dua snapshot; kolom float diberi toleransi pembulatan."""
beda = []
if list(sekarang.columns) != list(golden.columns):
return [f"kolom berubah: {list(sekarang.columns)}"]
if len(sekarang) != len(golden):
return [f"jumlah baris berubah: {len(golden)} -> {len(sekarang)}"]
for kolom in sekarang.columns:
if pd.api.types.is_float_dtype(sekarang[kolom]):
meleset = (sekarang[kolom] - golden[kolom]).abs() > 0.01
else:
meleset = sekarang[kolom] != golden[kolom]
if meleset.any():
i = meleset.idxmax()
beda.append(f"'{kolom}' baris {i}: {golden[kolom][i]} -> {sekarang[kolom][i]}")
return beda
def test_ringkasan_sesuai_golden():
sekarang = hitung_ringkasan(buat_df_pesanan())
if os.environ.get("PERBARUI_GOLDEN") == "1":
GOLDEN.parent.mkdir(exist_ok=True)
sekarang.to_csv(GOLDEN, index=False)
return
assert GOLDEN.exists(), "jalankan PERBARUI_GOLDEN=1 sekali untuk membuat golden"
golden = pd.read_csv(GOLDEN)
beda = banding_dengan_golden(sekarang, golden)
assert beda == [], "\n".join(beda)Mari kita telaah baris-baris kode di atas:
- Baris 15-21 pada
transform.py(hitung_ringkasan): agregat groupby plus rata-rata dua desimal — angka inilah yang dikunci oleh golden. - Baris 12-27 (
banding_dengan_golden): kolom float dibandingkan dengan toleransi0.01. Pergeseran kecil dari orde operasi floating-point lolos; perubahan perilaku nyata — pembulatan, rumus, satuan — terlihat sebagai baris lama menjadi baru. - Baris 32-35 (cabang
PERBARUI_GOLDEN): jalur pembaruan golden; test berhenti setelah menulis berkas. Jalur ini hanya boleh dipakai setelah diff ditinjau manusia. - Baris 39 (assert): daftar beda menjadi pesan kegagalan — persis yang terbaca pada output merah di bawah.
PERBARUI_GOLDEN=1 python3 -m pytest test_regression.py -qOutput yang Dihasilkan:
. [100%]
1 passed in 1.35sPerintah tersebut menulis golden/ringkasan.csv sekali, dan isinya menjadi kunci:
kota,jumlah_pesanan,total,rata_rata
Bandung,134,36908065,275433.32
Jakarta,122,31456351,257838.94
Medan,125,31692471,253539.77
Surabaya,119,32815523,275760.7python3 -m pytest test_regression.py -qOutput yang Dihasilkan:
. [100%]
1 passed in 1.42sSekarang datang skenario yang sesungguhnya: seorang pengembang mengubah pembulatan pada transform.py — .round(2) menjadi .round(0) — demi tampilan dashboard yang “lebih rapi”, tanpa memberi tahu tim keuangan. Perintah yang sama dijalankan ulang:
Output yang Dihasilkan:
F [100%]
=================================== FAILURES ===================================
_________________________ test_ringkasan_sesuai_golden _________________________
def test_ringkasan_sesuai_golden():
sekarang = hitung_ringkasan(buat_df_pesanan())
if os.environ.get("PERBARUI_GOLDEN") == "1":
GOLDEN.parent.mkdir(exist_ok=True)
sekarang.to_csv(GOLDEN, index=False)
return
assert GOLDEN.exists(), "jalankan PERBARUI_GOLDEN=1 sekali untuk membuat golden"
golden = pd.read_csv(GOLDEN)
beda = banding_dengan_golden(sekarang, golden)
> assert beda == [], "\n".join(beda)
E AssertionError: 'rata_rata' baris 0: 275433.32 -> 275433.0Test hanya melaporkan baris pertama yang meleset; durasi 1.35s–1.42s berubah-ubah antar eksekusi. Mengembalikan .round(2) membuat suite hijau kembali dalam hitungan detik.
Ketika Diff itu Disengaja #
Tidak semua beda adalah bug. Rumus diperbaiki, kolom ditambah, definisi rata-rata diubah atas kesepakatan — pada kasus itu golden yang harus ikut. Alurnya tetap tiga langkah: tinjau diff bersama konsumen output, jalankan PERBARUI_GOLDEN=1, lalu commit kode dan golden dalam satu pull request sehingga reviewer melihat keduanya berubah bersama. Sejarah git tetap menyimpan golden lama, jadi siapa pun dapat melihat kapan angka resmi berubah dan alasannya.
Rujukan resmi untuk pendalaman: dokumentasi pandas . Perilaku pipeline kini terkunci dan setiap diff punya jalur persetujuan; satu dimensi masih terbuka — waktu. Chapter Performance Testing mengubah durasi menjadi angka yang bisa diuji dengan ambang dan baseline.