Jurus Rahasia Refactoring Kode Warisan: Menggali Golden Master Testing
1. Pendahuluan
Pernahkah Anda merasa ngeri saat harus mengubah sebaris kode di aplikasi warisan (legacy application) yang sudah berjalan bertahun-tahun? Kode yang tidak memiliki tes, penuh dengan logika bisnis yang misterius, dan terasa seperti bom waktu yang siap meledak kapan saja? Anda tidak sendirian. Ini adalah skenario umum yang dihadapi banyak developer.
Refactoring kode seperti ini adalah kebutuhan mutlak untuk menjaga aplikasi tetap sehat, scalable, dan mudah dirawat. Namun, melakukannya tanpa jaring pengaman yang memadai adalah tindakan bunuh diri. Bagaimana kita bisa yakin bahwa perubahan yang kita buat tidak merusak fungsionalitas yang sudah ada, terutama jika tidak ada dokumentasi yang jelas atau orang yang memahami sepenuhnya semua side effect dari kode tersebut?
Di sinilah Golden Master Testing, atau sering juga disebut Characterization Testing, datang sebagai pahlawan tanpa tanda jasa. Ini adalah teknik yang memungkinkan kita untuk mulai merefaktor kode warisan dengan percaya diri, bahkan ketika kita tidak memiliki satu pun tes yang ditulis sebelumnya. Mari kita selami lebih dalam bagaimana jurus rahasia ini bisa menyelamatkan Anda dari mimpi buruk kode legacy.
2. Apa Itu Golden Master Testing?
📌 Golden Master Testing (GMT) adalah sebuah pendekatan testing di mana kita menangkap perilaku yang ada dari sebuah sistem atau bagian kode yang kompleks, lalu menggunakan output yang ditangkap tersebut sebagai “golden master” atau baseline. Kemudian, setiap perubahan yang kita buat pada kode akan dibandingkan dengan golden master ini. Jika output setelah perubahan berbeda dari golden master, maka tes gagal, menandakan bahwa perilaku sistem telah berubah.
💡 Tujuan utama GMT bukanlah untuk memverifikasi kebenaran atau spesifikasi kode (karena spesifikasi itu mungkin tidak pernah ada!), melainkan untuk memastikan bahwa perilaku yang ada dari kode tersebut tidak berubah secara tidak sengaja selama proses refactoring. Ini sangat krusial saat kita berhadapan dengan kode yang sudah bekerja di produksi, tetapi kita tidak tahu mengapa atau bagaimana persisnya ia bekerja.
Bayangkan Anda memiliki sebuah mesin tua yang sangat penting. Mesin itu bekerja dengan baik selama bertahun-tahun, tapi tidak ada blueprint atau manual instruksi. Anda ingin memodifikasi salah satu bagiannya agar lebih efisien. Sebelum membongkar, Anda akan mencatat setiap detail perilaku mesin: suara yang dihasilkan, getaran, kecepatan putaran, dan semua output yang keluar. Catatan detail ini adalah “golden master” Anda. Ketika Anda memodifikasi mesin, Anda akan terus membandingkan perilaku barunya dengan catatan “golden master” Anda. Jika ada perbedaan yang tidak disengaja, Anda tahu ada yang salah.
GMT berfokus pada “apa yang kode lakukan sekarang” daripada “apa yang kode seharusnya lakukan”. Ini adalah langkah awal untuk mendapatkan kontrol atas kode warisan, sebelum kita bisa mulai menulis unit test yang lebih spesifik dan memvalidasi kebenaran perilaku.
3. Kapan Menggunakan Golden Master Testing?
GMT adalah alat yang sangat spesifik dan paling efektif dalam skenario tertentu.
🎯 Kasus Ideal untuk Golden Master Testing:
- Kode Legacy Tanpa Tes: Ini adalah skenario paling umum. Anda memiliki modul atau fungsi penting yang tidak dilindungi oleh tes otomatis apa pun.
- Kompleksitas Tinggi: Bagian kode yang memiliki banyak percabangan logika, side effect yang tidak jelas, atau berinteraksi dengan banyak bagian sistem lain.
- Perubahan Berisiko Tinggi: Anda perlu merefaktor kode yang sangat kritikal atau yang sering menimbulkan bug di masa lalu.
- Memahami Perilaku Sistem: Terkadang, bahkan sebelum refactoring, Anda perlu memahami bagaimana sebuah sistem berperilaku di berbagai kondisi input. GMT bisa membantu “mengkristalkan” pemahaman tersebut.
- Jembatan Menuju Unit Testing: Setelah bagian kode “dijinakkan” dengan GMT dan direfaktor menjadi lebih modular, Anda bisa mulai menulis unit test yang lebih granular dan spesifik untuk setiap unit baru.
❌ Kapan GMT Bukan Solusi Utama (atau satu-satunya):
- Proyek Baru: Untuk proyek baru, Anda harus selalu memulai dengan Test-Driven Development (TDD) atau setidaknya unit/integrasi test yang komprehensif. GMT adalah alat pemulihan, bukan pencegahan.
- Kode yang Sudah Memiliki Tes Memadai: Jika kode Anda sudah dilindungi oleh unit test yang baik, GMT mungkin tidak diperlukan atau hanya akan menambah overhead pemeliharaan.
- Memvalidasi Kebenaran Spesifikasi: Ingat, GMT hanya memvalidasi perilaku yang ada. Jika perilaku yang ada itu bug, GMT akan mengabadikan bug tersebut. Anda tetap perlu pemahaman bisnis dan koreksi bug secara manual.
GMT adalah alat yang sangat berharga untuk menciptakan jaring pengaman sementara yang memungkinkan Anda merefaktor dengan aman. Ini adalah investasi awal yang akan sangat menguntungkan di kemudian hari, terutama saat Anda berjuang untuk mendapatkan pemahaman dan kontrol atas kode warisan yang menakutkan.
4. Langkah-langkah Menerapkan Golden Master Testing
Menerapkan Golden Master Testing cukup lugas, meskipun membutuhkan sedikit ketelitian di awal. Berikut adalah langkah-langkah praktisnya:
4.1. Identifikasi Target Refactoring
Pilih bagian kode yang paling ingin Anda refactor. Ini bisa berupa satu fungsi, sebuah kelas, atau bahkan sebuah modul. Mulailah dari yang kecil dan paling kritikal.
4.2. Buat “Golden Master”
Ini adalah langkah paling penting. Anda perlu menjalankan kode target dengan serangkaian input yang representatif dan menangkap semua outputnya.
-
Siapkan Input Representatif:
- Pikirkan berbagai skenario penggunaan kode Anda.
- Sertakan input “normal”, “edge case” (misalnya, nilai nol, string kosong, daftar kosong), dan input yang memicu jalur kode yang berbeda.
- Semakin banyak variasi input yang Anda siapkan, semakin kuat golden master Anda.
-
Jalankan Kode dan Tangkap Output:
- Jalankan kode target dengan setiap input yang telah Anda siapkan.
- Tangkap outputnya. Output ini bisa beragam:
- String/JSON: Jika kode menghasilkan data terstruktur.
- File: Jika kode menghasilkan file (laporan, gambar, dll.).
- Screenshot: Untuk komponen UI atau output visual.
- Log: Jika perilaku penting tercatat di log.
- Database State: Perubahan pada database (meskipun ini lebih kompleks).
- Simpan output yang ditangkap ini sebagai “golden master” Anda. Idealnya, simpan dalam format yang mudah dibaca dan dibandingkan (misalnya, file
.json,.txt, atau.snapshot).
💡 Contoh (Pseudo-code):
// Misalkan ini adalah fungsi legacy yang ingin kita refactor
function processLegacyData(data) {
// ... logika kompleks dengan banyak side effect ...
return `Processed: ${JSON.stringify(data).toUpperCase()} - ${new Date().getFullYear()}`; // Contoh output
}
// Input representatif
const inputs = [
{ id: 1, name: "Alice", value: 100 },
{ id: 2, name: "Bob", value: 0 },
{ id: 3, name: "", value: -50 }, // edge case
];
const goldenMasters = {};
inputs.forEach((input, index) => {
goldenMasters[`input_${index}`] = processLegacyData(input);
});
// goldenMasters sekarang berisi output dari setiap input
// Contoh: { "input_0": "PROCESSED: {\"ID\":1,\"NAME\":\"ALICE\",\"VALUE\":100} - 2023", ... }
// Simpan ini ke file atau variabel untuk perbandingan.
⚠️ Penting: Pastikan output yang Anda tangkap bersifat deterministik. Hindari elemen yang berubah setiap kali dijalankan (misalnya, timestamp, ID acak, atau urutan elemen dalam set yang tidak diurutkan) kecuali jika Anda memiliki cara untuk menormalisasinya.
4.3. Tulis Tes Perbandingan
Setelah Anda memiliki golden master, langkah selanjutnya adalah menulis tes otomatis yang akan membandingkan output kode saat ini dengan golden master yang tersimpan.
- Buat Test Case: Untuk setiap input yang Anda gunakan untuk membuat golden master, buatlah sebuah test case.
- Jalankan Kode Lagi: Di dalam test case, jalankan kode target dengan input yang sama.
- Bandingkan Output: Bandingkan output yang dihasilkan saat ini dengan golden master yang sesuai.
✅ Contoh dengan Jest (JavaScript):
Jest memiliki fitur snapshot testing yang sangat cocok untuk Golden Master Testing.
// __tests__/legacyProcessor.test.js
const processLegacyData = require('../legacyProcessor'); // Asumsikan fungsi di file terpisah
describe('Legacy Data Processor - Golden Master Tests', () => {
const inputs = [
{ id: 1, name: "Alice", value: 100 },
{ id: 2, name: "Bob", value: 0 },
{ id: 3, name: "", value: -50 },
{ id: 4, name: "Charlie", value: 200, options: { debug: true } }, // Input lebih kompleks
];
// Untuk memastikan output deterministik, kita bisa mock tanggal jika ada di output
const mockDate = new Date('2023-01-01T00:00:00.000Z');
const originalDate = global.Date;
beforeAll(() => {
global.Date = class extends originalDate {
constructor(dateString) {
if (dateString) {
return new originalDate(dateString);
}
return mockDate;
}
};
});
afterAll(() => {
global.Date = originalDate;
});
inputs.forEach((input, index) => {
test(`should produce consistent output for input case ${index}`, () => {
const actualOutput = processLegacyData(input);
// Ini akan membuat atau membandingkan snapshot dengan file .snap
expect(actualOutput).toMatchSnapshot();
});
});
});
Saat pertama kali Anda menjalankan tes ini (jest), Jest akan membuat file __snapshots__/legacyProcessor.test.js.snap yang berisi “golden master” Anda. Setiap kali Anda menjalankan tes selanjutnya, Jest akan membandingkan output baru dengan snapshot ini.
4.4. Refactor dengan Percaya Diri
Setelah semua tes Golden Master Anda berhasil (hijau), Anda memiliki jaring pengaman! Sekarang Anda bisa mulai merefaktor kode target.
- Perubahan Kecil, Jalankan Tes: Lakukan perubahan kecil pada kode Anda, lalu jalankan tes Golden Master.
- Periksa Kegagalan:
- Jika tes gagal, berarti Anda telah mengubah perilaku kode.
- Jika perubahan perilaku itu disengaja (misalnya, Anda memperbaiki bug atau memang ingin mengubah logika), maka Anda perlu memperbarui golden master Anda (dengan Jest, Anda bisa menjalankan
jest -u). - Jika perubahan perilaku itu tidak disengaja, berarti Anda telah memperkenalkan bug. Perbaiki kode Anda sampai tes Golden Master kembali hijau.
Dengan pendekatan ini, Anda bisa merefaktor kode legacy selangkah demi selangkah, dengan keyakinan bahwa setiap perubahan tidak merusak fungsionalitas yang sudah ada.
5. Contoh Konkret: Refactoring Fungsi Pemformatan Laporan
Mari kita ambil contoh yang lebih praktis. Bayangkan Anda memiliki fungsi lama yang mengambil data pesanan dan memformatnya menjadi string laporan yang kompleks.
// orderFormatter.js (Kode Legacy)
function formatOrderReport(order) {
let report = `--- Laporan Pesanan #${order.id} ---\n`;
report += `Nama Pelanggan: ${order.customerName}\n`;
report += `Tanggal: ${new Date(order.orderDate).toLocaleDateString('id-ID')}\n`; // Ada dependensi tanggal
report += `Item:\n`;
let total = 0;
order.items.forEach(item => {
report += ` - ${item.name} (${item.qty}x) @ Rp${item.price.toLocaleString('id-ID')}\n`;
total += item.qty * item.price;
});
report += `Total Pembayaran: Rp${total.toLocaleString('id-ID')}\n`;
// Ada logika diskon aneh yang tersembunyi
if (order.customerName.includes("VIP")) {
const discount = total * 0.1;
report += `Diskon VIP (10%): Rp${discount.toLocaleString('id-ID')}\n`;
total -= discount;
report += `Total Setelah Diskon: Rp${total.toLocaleString('id-ID')}\n`;
}
report += `Status: ${order.status.toUpperCase()}\n`;
report += `----------------------------\n`;
return report;
}
module.exports = formatOrderReport;
Fungsi ini memiliki banyak masalah: dependensi tanggal global, logika diskon yang bercampur, dan sulit dibaca. Kita ingin merefaktornya menjadi lebih modular dan testable.
5.1. Membuat Golden Master
Pertama, siapkan input dan tangkap outputnya.
// orderFormatter.test.js
const formatOrderReport = require('./orderFormatter');
describe('formatOrderReport - Golden Master Tests', () => {
const mockOrders = [
{
id: 'A001',
customerName: 'Budi Santoso',
orderDate: '2023-03-15T10:00:00Z',
items: [
{ name: 'Laptop', qty: 1, price: 12000000 },
{ name: 'Mouse', qty: 2, price: 150000 },
],
status: 'completed',
},
{
id: 'B002',
customerName: 'Siti VIP', // Memicu diskon VIP
orderDate: '2023-03-16T11:30:00Z',
items: [
{ name: 'Keyboard Mekanik', qty: 1, price: 800000 },
],
status: 'pending',
},
{
id: 'C003',
customerName: 'Joko',
orderDate: '2023-03-17T09:00:00Z',
items: [], // Pesanan kosong
status: 'cancelled',
},
];
// Mock Date untuk konsistensi di output
const mockDate = new Date('2023-03-15T00:00:00.000Z');
const originalDate = global.Date;
beforeAll(() => {
global.Date = class extends originalDate {
constructor(dateString) {
if (dateString) {
return new originalDate(dateString);
}
return mockDate;
}
};
});
afterAll(() => {
global.Date = originalDate;
});
mockOrders.forEach((order, index) => {
test(`should generate consistent report for order case ${index}`, () => {
const actualReport = formatOrderReport(order);
expect(actualReport).toMatchSnapshot();
});
});
});
Jalankan jest. Ini akan menghasilkan file snapshot yang berisi laporan yang diformat untuk setiap pesanan. File snapshot ini adalah “golden master” kita.
5.2. Refactoring dan Verifikasi
Sekarang, kita bisa mulai merefaktor formatOrderReport. Misalnya, kita ingin memisahkan logika diskon dan pemformatan tanggal.
// orderFormatter.js (Setelah Refactoring Awal)
function calculateDiscount(total, customerName) {
if (customerName.includes("VIP")) {
return total * 0.1;
}
return 0;
}
function formatCurrency(amount) {
return `Rp${amount.toLocaleString('id-ID')}`;
}
function formatDate(dateString) {
return new Date(dateString).toLocaleDateString('id-ID');
}
function formatOrderReport(order) {
let report = `--- Laporan Pesanan #${order.id} ---\n`;
report += `Nama Pelanggan: ${order.customerName}\n`;
report += `Tanggal: ${formatDate(order.orderDate)}\n`; // Menggunakan fungsi baru
report += `Item:\n`;
let total = 0;
order.items.forEach(item => {
report += ` - ${item.name} (${item.qty}x) @ ${formatCurrency(item.price)}\n`;
total += item.qty * item.price;
});
report += `Total Pembayaran: ${formatCurrency(total)}\n`;
const discount = calculateDiscount(total, order.customerName); // Menggunakan fungsi baru
if (discount > 0) {
report += `Diskon VIP (10%): ${formatCurrency(discount)}\n`;
total -= discount;
report += `Total Setelah Diskon: ${formatCurrency(total)}\n`;
}
report += `Status: ${order.status.toUpperCase()}\n`;
report += `----------------------------\n`;
return report;
}
module.exports = formatOrderReport;
Setelah refactoring, jalankan lagi jest. Jika semua tes hijau, berarti refactoring Anda tidak mengubah perilaku yang ada. Jika ada tes yang gagal, berarti Anda secara tidak sengaja mengubah output. Anda bisa memeriksa perbedaan di laporan Jest atau di file .snap untuk melihat apa yang berubah.
Dengan cara ini, kita bisa merefaktor bagian demi bagian, sambil selalu memiliki jaring pengaman yang memastikan kita tidak merusak fungsionalitas yang sudah bekerja.
6. Tips dan Best Practices untuk Golden Master Testing
Untuk memaksimalkan efektivitas Golden Master Testing, perhatikan tips berikut:
- 🎯 Pilih Input yang Komprehensif: Semakin banyak dan bervariasi input yang Anda gunakan untuk membuat golden master, semakin kuat jaring pengaman Anda. Jangan hanya fokus pada kasus “happy path”, sertakan juga kasus-kasus ekstrem atau yang jarang terjadi.
- ✅ Pastikan Output Deterministic: Ini sangat krusial. Jika output Anda mengandung elemen acak (misalnya, ID unik yang dihasilkan setiap kali, tanggal/waktu saat ini, urutan item dalam array yang tidak diurutkan), tes Anda akan sering gagal tanpa alasan yang jelas. Mock atau normalisasi elemen-elemen ini jika memungkinkan.
- 💾 Kelola Golden Master di Version Control: Simpan file golden master (misalnya, file
.snapJest) di sistem kontrol versi Anda (Git). Ini memastikan semua anggota tim menggunakan baseline yang sama dan perubahan pada golden master juga tercatat. - ⚠️ Perbarui Golden Master dengan Hati-hati: Ketika Anda sengaja mengubah perilaku kode (misalnya, memperbaiki bug, menambahkan fitur), Anda perlu memperbarui golden master. Lakukan ini dengan kesadaran penuh, pastikan perubahan perilaku memang yang Anda inginkan. Jangan memperbarui golden master secara membabi buta.
- 📌 Batasi Lingkup Awal: Mulailah dengan menerapkan GMT pada unit kode yang paling kecil dan paling kritikal yang ingin Anda refactor. Setelah Anda merasa nyaman, Anda bisa memperluas cakupannya.
- 💡 Jangan Berhenti di Sini: Golden Master Testing adalah jembatan, bukan tujuan akhir. Setelah Anda berhasil merefaktor bagian kode dan membuatnya lebih bersih serta modular, pertimbangkan untuk mengganti atau melengkapi GMT dengan unit test yang lebih granular dan spesifik. Unit test yang baik memverifikasi kebenaran dan niat kode, bukan hanya perilaku yang ada.
- 🚀 Integrasikan ke CI/CD: Pastikan tes Golden Master Anda berjalan secara otomatis di pipeline Continuous Integration/Continuous Deployment (CI/CD) Anda. Ini akan memberikan umpan balik instan kepada developer jika ada perubahan perilaku yang tidak disengaja.
7. Kelebihan dan Kekurangan Golden Master Testing
Seperti setiap alat, GMT memiliki kekuatan dan kelemahan:
Kelebihan:
- Cepat Diterapkan: Anda bisa mendapatkan perlindungan tes dalam waktu singkat, bahkan untuk codebase yang besar dan tidak memiliki tes sama sekali.
- Jaring Pengaman Instan: Memberikan rasa aman yang sangat dibutuhkan saat merefaktor kode yang menakutkan.
- Meningkatkan Kepercayaan Diri Developer: Mengurangi ketakutan akan memperkenalkan regresi, sehingga developer lebih berani merefaktor dan meningkatkan kualitas kode.
- Membantu Memahami Perilaku Sistem: Dengan mengamati output dari berbagai input, Anda secara tidak langsung belajar tentang bagaimana kode legacy bekerja.
Kekurangan:
- Tidak Memvalidasi Kebenaran: GMT hanya memverifikasi bahwa perilaku tidak berubah. Jika ada bug di perilaku asli, GMT akan mengabadikan bug tersebut.
- Maintenance yang Potensial Tinggi: Jika output