Piramida Testing & Strategi — Testing

Pernah deploy ke production hari Jumat sore, terus jam 6 user complain "tombol checkout error"? Atau refactor code yang katanya cuma "tambahin null check"…

Test adalah kode kecil yang menjalankan kodemu lalu memeriksa hasilnya secara otomatis. Daripada buka browser dan klik-klik tiap habis mengubah sesuatu, kamu jalankan satu perintah, dan dalam hitungan detik kamu tahu mana yang masih benar dan mana yang rusak. Bayangin warung bakso yang mau memastikan semangkuknya enak. Kokinya bisa mencicipi kuah, bakso, dan mie satu per satu: cepat, dan kalau ada yang aneh langsung ketahuan bagian mana. Bisa mencicipi kuah dan bakso bareng, untuk tahu cocok atau gak kalau dicampur. Atau menyajikan semangkuk penuh ke pelanggan lalu menunggu reaksinya: paling nyata, tapi lama, dan kalau pelanggan bilang "kurang enak", kamu belum tahu salahnya di mana. Tiga cara itu persis tiga level testing.

🧪 Tebakan cepat: 100 unit test yang masing-masing butuh 5 ms, atau 10 E2E test yang masing-masing butuh 8 detik. Mana yang selesai duluan, dan selisihnya berapa? Jawabannya ada di bagian "Kenapa bentuknya piramida".

Test pertamamu

Contohnya toko online. Fungsi ini menghitung total belanja setelah diskon:

function hitungTotal(items, diskonPersen) {
  const subtotal = items.reduce((jumlah, item) => jumlah + item.harga * item.qty, 0);
  return subtotal - (subtotal * diskonPersen) / 100;
}

describe("hitungTotal", () => {
  it("memotong diskon 10%", () => {
    expect(hitungTotal([{ harga: 50000, qty: 2 }], 10)).toBe(90000);
  });
});

Yang di dalam expect(...) harus pemanggilan fungsinya. expect(90000).toBe(90000) selalu lulus, karena gak ada kode yang benar-benar diperiksa.

Kalau suatu hari baris terakhirnya salah ketik jadi return subtotal - diskonPersen;, hasilnya 99.990, dan test runner (program yang menjalankan semua test, misalnya Vitest) melapor:

 × checkout.test.js > hitungTotal > memotong diskon 10% 2ms
   → expected 99990 to be 90000 // Object.is equality

Untuk fungsi async (yang menjawab lewat Promise, misalnya mengambil data dari database), beri async pada function test-nya dan tunggu hasilnya dengan await:

async function ambilPesanan(id) {
  return { id, total: 90000 };
}

describe("ambilPesanan", () => {
  it("mengembalikan pesanan dengan id yang diminta", async () => {
    const pesanan = await ambilPesanan(7);
    expect(pesanan.id).toBe(7);
  });
});

Tanpa await, pesanan masih berupa Promise, pesanan.id jadi undefined, dan test-nya gagal dengan expected undefined to be 7.

Tiga level: unit, integration, E2E

Level Yang diuji Contoh di toko online Kata yang sering ada di nama test-nya Waktu per test
Unit satu fungsi, sendirian hitungTotal, formatRupiah fungsi, hitung, format sekitar 2 ms
Integration interaksi antar komponen atau modul route POST /api/orders yang memvalidasi lalu menyimpan ke database tes API, endpoint, database, controller ratusan ms
E2E alur lengkap dari sisi pengguna, lewat browser sungguhan buka keranjang, klik Bayar, lihat total klik, form, browser, halaman beberapa detik
test("pembeli klik Bayar dan melihat total", async ({ page }) => {
  await page.goto("/keranjang");
  await page.getByRole("button", { name: "Bayar" }).click();
  await expect(page.getByText("Total: Rp90.000")).toBeVisible();
});

Ini yang terjadi kalau satu bug ditanam di toko yang sama, dengan 7 unit, 2 integration, dan 1 E2E test:

Bug yang ditanam Unit (7) Integration (2) E2E (1)
hitungTotal mengurangi 10 rupiah, bukan 10% 2 gagal, dua-duanya di hitungTotal lulus gagal: Total: Rp90.000 gak muncul
route API crash saat menyimpan lulus 1 gagal: expected 500 to be 201 gagal
tombol Bayar lupa dipasangi handler lulus lulus gagal

Unit test menunjuk persis fungsi yang salah. E2E menangkap ketiganya, termasuk bug tombol yang gak kelihatan dari level lain, tapi pesannya cuma "total yang diharapkan gak muncul", jadi kamu masih harus mencari sendiri sumbernya.

Kenapa bentuknya piramida

Piramida testing adalah panduan proporsi: banyak test murah di dasar, sedikit test mahal di puncak. Patokan umumnya 70% unit, 20% integration, 10% E2E. Dari 10 test: 7 unit, 2 integration, 1 E2E.

Alasannya waktu dan kejelasan. 100 unit test × 5 ms = 0,5 detik. 10 E2E test × 8 detik = 80 detik. Unit test selesai duluan, selisihnya 79,5 detik, dan itu terasa tiap kali kamu menyimpan file. Itu jawaban tebakannya.

Jadi tulis unit test untuk logika (hitungan, validasi, format), integration test untuk tiap endpoint penting, dan E2E cuma untuk alur kritis: login, checkout, pendaftaran.

⚠️ Jebakan yang sering ketemu

TL;DR: Test adalah kode yang menjalankan kodemu dan memeriksa hasilnya otomatis: describe mengelompokkan, it atau test adalah satu test, expect(hasil).toBe(harapan) membandingkan, dan fungsi async ditunggu dengan await. Ada tiga level: unit (satu fungsi, paling cepat, paling banyak, di dasar piramida), integration (interaksi antar komponen, misalnya API dan database), dan E2E (alur pengguna di browser, paling lambat dan mahal, di puncak). Patokannya 70% unit, 20% integration, 10% E2E. Kalau cuma mengandalkan E2E, test-nya lambat dan sulit menunjuk sumber bug.

Yang akan kamu pelajari