12.8 Testing sebagai Bukti, Bukan Sekadar Kewajiban

Setelah kode direview, senior engineer memastikan perubahan itu benar-benar bekerja melalui testing yang tepat. Banyak tim menulis test karena aturan proyek mengharuskan coverage minimal delapan puluh persen. Mereka menulis test demi memenuhi metrik, bukan demi mengumpulkan bukti. Akibatnya, test yang ada seringkali hanya mengulang logika kode tanpa benar-benar memverifikasi perilaku sistem.

Bayangkan Anda baru saja menyelesaikan perubahan pada modul pembayaran. Anda sudah menulis unit test yang mencakup semua cabang kondisi di dalam fungsi kalkulasi diskon. Coverage terlihat bagus, semua baris kode tereksekusi. Tapi ketika integrasi dengan gateway pembayaran eksternal diuji, ternyata format request body yang Anda kirim tidak sesuai dengan spesifikasi API pihak ketiga. Unit test tidak menangkap ini karena ia hanya menguji fungsi dalam isolasi, tanpa koneksi nyata ke luar.

Perhatikan perbedaan dua pendekatan test berikut untuk fungsi kalkulasi diskon:

// Test buruk: hanya mengulang logika, tidak memberi bukti
const calculateDiscount = (price, discountPercent) => price - (price * discountPercent) / 100;

test('calculateDiscount returns correct value', () => {
  expect(calculateDiscount(100, 10)).toBe(90); // hanya mengulang: 100 - 10 = 90
});

// Test baik: memverifikasi kasus tepi dan perilaku
const calculateDiscountSafe = (price, discountPercent) => {
  if (price < 0) throw new Error('Harga tidak boleh negatif');
  if (discountPercent < 0 || discountPercent > 100) throw new Error('Diskon harus 0-100');
  return price - (price * discountPercent) / 100;
};

test('calculateDiscountSafe handles edge cases', () => {
  expect(calculateDiscountSafe(0, 10)).toBe(0);
  expect(() => calculateDiscountSafe(-100, 10)).toThrow('Harga tidak boleh negatif');
  expect(() => calculateDiscountSafe(100, 150)).toThrow('Diskon harus 0-100');
  expect(calculateDiscountSafe(200, 0)).toBe(200);
  expect(calculateDiscountSafe(200, 100)).toBe(0);
});

Senior engineer memilih jenis testing berdasarkan risiko, bukan berdasarkan target coverage. Ia bertanya: apa yang paling mungkin rusak? Apa yang paling mahal jika gagal? Jawaban atas pertanyaan itu menentukan jenis test yang ditulis.

Berikut adalah diagram alur yang merangkum cara senior engineer memutuskan jenis test berdasarkan risiko dan biaya kegagalan.

flowchart TD A[Mulai: Apa yang paling mungkin rusak?] --> B{Apa yang paling mahal jika gagal?} B --> C[Logika bisnis murni] B --> D[Komunikasi eksternal] B --> E[Format pesan antar layanan] B --> F[Jalur pengguna paling kritis] B --> G[Setelah deployment] B --> H[Di production terus-menerus] C --> I[Unit test] D --> J[Integration test] E --> K[Contract test] F --> L[End-to-end test] G --> M[Smoke test] H --> N[Production check] I --> O[Verifikasi manual oleh engineer] J --> O K --> O L --> O M --> O N --> O O --> P[Test sebagai bukti, bukan kewajiban]

Unit test digunakan untuk memverifikasi logika bisnis murni: kalkulasi, validasi, transformasi data, keputusan kondisional yang tidak bergantung pada sistem eksternal. Integration test memastikan bahwa kode berkomunikasi dengan benar ke database, message queue, atau API eksternal. Contract test memverifikasi bahwa format pesan yang dikirim dan diterima sesuai dengan kesepakatan antar layanan. End-to-end test mengalirkan skenario lengkap dari input pengguna hingga output akhir, tetapi hanya untuk jalur paling kritis.

Smoke test dijalankan setelah deployment untuk memastikan sistem bisa melayani request dasar. Production check berjalan terus-menerus di lingkungan nyata, memverifikasi bahwa endpoint penting merespons dengan benar, latensi wajar, dan data konsisten. Post-release verification dilakukan beberapa jam atau hari setelah rilis, untuk memastikan tidak ada efek samping yang baru muncul setelah traffic nyata mengalir.

AI bisa membantu membuat test awal. Anda bisa memberikan potongan kode ke AI assistant dan meminta draft unit test untuk fungsi tertentu. Atau meminta AI menulis integration test berdasarkan spesifikasi API. Hasilnya cepat, rapi, dan mencakup banyak kasus. Tapi AI tidak tahu konteks bisnis sistem Anda. Ia tidak tahu bahwa skenario tertentu sangat jarang terjadi tetapi sangat kritis jika gagal. Ia tidak tahu bahwa kombinasi kondisi tertentu pernah menyebabkan insiden di production dua bulan lalu.

Verifikasi tetap di tangan manusia. Senior engineer membaca setiap test yang dihasilkan AI, memeriksa apakah skenario yang diuji benar-benar relevan, apakah asumsi yang dipakai sesuai dengan perilaku nyata sistem, dan apakah test tersebut benar-benar memberikan bukti bahwa perubahan bekerja. Ia tidak menerima test hanya karena terlihat rapi dan coverage naik.

Testing yang baik bukan kumpulan kode yang memenuhi aturan. Ia adalah kumpulan bukti bahwa sistem bekerja sesuai harapan. Setiap test adalah jawaban atas pertanyaan: bagaimana aku tahu bahwa perubahan ini tidak merusak sesuatu? Semakin jelas jawabannya, semakin percaya diri Anda merilis ke production.

Setelah testing selesai dan perubahan dirilis, pekerjaan senior engineer belum berakhir. Ia masih harus belajar dari production: apakah asumsinya terbukti benar? Apakah test yang ditulis cukup menangkap risiko nyata? Inilah lingkaran umpan balik yang akan kita bahas selanjutnya.