Testing untuk Developer Malas: Cara Menulis Test yang Tidak Menyiksa

Developer menulis unit test untuk software quality

Written by

in

Saya akan jujur dulu: saya dulu anti-test.

Bukan karena tidak tahu manfaatnya. Tapi karena pengalaman awal saya dengan testing itu menyiksa — menulis test lebih lama dari menulis kode aslinya, test-nya rapuh (sering fail padahal kode benar), dan setiap refactor kecil harus update puluhan test.

Sampai seseorang beri saya perspektif yang mengubah cara pandang: test yang baik seharusnya terasa seperti documentation yang bisa di-run, bukan beban tambahan.

Kalau testing terasa menyiksa, masalahnya bukan di testing — tapi di cara kita menulis test.

Kenapa Developer Malas Menulis Test (dan Kenapa Itu Wajar)

Sebelum soal “bagaimana”, kita pahami dulu “kenapa tidak”:

Waktu jangka pendek terasa lebih berharga. Feature harus jalan hari ini, tidak ada waktu untuk test. Ini pemikiran yang logis secara lokal tapi mahal secara keseluruhan.

Test yang ditulis dengan terburu-buru itu buruk. Kalau test pertama kamu seumur hidup itu brittle dan maintenance-heavy, wajar kalau kamu tidak mau menulis test lagi.

Tidak ada contoh yang baik di codebase. Kalau semua kode yang ada di tempat kerja tidak ada test-nya, sulit untuk tahu harus mulai dari mana dan seperti apa standar yang baik.

Testing terasa seperti certify bahwa kode sudah benar — dan kita takut salah, jadi kita tunda.

Semua ini valid. Dan semua ini bisa diatasi dengan perubahan pendekatan.

Prinsip Testing untuk yang Pragmatis

1. Test Behavior, Bukan Implementation

Ini perbedaan yang paling penting dan paling sering salah dipahami.

Test yang buruk (test implementation):

test('memanggil helper function dengan parameter benar', () => {

const spy = jest.spyOn(utils, 'formatCurrency');

calculateTotal([item1, item2]);

expect(spy).toHaveBeenCalledWith(25000);

});

Test yang baik (test behavior):

test('menghitung total harga dengan benar', () => {

const result = calculateTotal([

{ price: 10000, qty: 1 },

{ price: 15000, qty: 1 }

]);

expect(result).toBe(25000);

});

Test pertama akan fail kalau kamu refactor cara internals-nya bekerja — meski outputnya tetap sama. Test kedua hanya peduli apakah outputnya benar.

Ini yang membuat test menjadi maintenance nightmare: terlalu terikat ke implementation details.

2. AAA Pattern — Arrange, Act, Assert

Struktur test yang konsisten membuatnya mudah dibaca dan ditulis:

test('user berhasil login dengan credential yang valid', async () => {

// Arrange — setup kondisi awal

const user = { email: '[email protected]', password: 'validpass123' };

await createTestUser(user);

// Act — lakukan tindakan yang ditest

const result = await loginUser(user.email, user.password);

// Assert — verifikasi hasilnya

expect(result.success).toBe(true);

expect(result.token).toBeDefined();

});

Tiga bagian yang jelas — tidak ada logika tersembunyi, tidak ada ambiguitas.

3. One Test, One Concern

Setiap test harus test satu hal. Kalau test kamu panjang dan ada multiple expect, pisah jadi beberapa test yang lebih kecil.

Test yang fokus pada satu concern:

  • Lebih mudah dibaca (nama test bisa sangat spesifik)
  • Kalau fail, langsung tahu masalahnya di mana
  • Lebih mudah di-maintain

4. Test Name sebagai Documentation

Nama test adalah dokumentasi yang selalu up-to-date — karena kalau kode berubah tapi nama test tidak mencerminkan behavior sebenarnya, test itu useless.

Gunakan konvensi deskriptif:

✓ BAIK: "menolak login ketika password kurang dari 8 karakter"

✗ BURUK: "test login gagal"

✓ BAIK: "mengirim email konfirmasi setelah user register"

✗ BURUK: "test register"

Jenis Test dan Kapan Masing-masing Dipakai

Developer sering terjebak di debat “unit test vs integration test vs e2e.” Jawaban pragmatis: semua perlu, dalam proporsi yang tepat.

Piramida Testing

/\

/E2E\ <- Sedikit, lambat, mahal tapi penting

/------\

/ Integ \ <- Sedang

/----------\

/ Unit Test \ <- Banyak, cepat, murah

/--------------\

Unit Test — test fungsi/method secara isolasi. Cepat, granular, mudah di-debug. Prioritaskan untuk:

  • Business logic yang kompleks
  • Fungsi dengan banyak edge case
  • Kalkulasi atau transformasi data

Integration Test — test bagaimana beberapa komponen bekerja bersama. Lebih lambat tapi catches bugs yang unit test miss. Prioritaskan untuk:

  • Alur auth/login
  • Database queries
  • API endpoints

End-to-End (E2E) Test — test dari perspektif user, biasanya pakai tool seperti Playwright atau Cypress. Lambat dan kadang brittle, tapi paling mirip dengan pengalaman user sesungguhnya. Prioritaskan untuk:

  • Happy path dari fitur critical (checkout, signup, login)
  • Skenario yang melibatkan banyak layer sekaligus

Mulai dari Mana Kalau Codebase Tidak Punya Test

Ini situasi yang paling umum: kamu join project atau punya proyek lama yang zero test. Dari mana mulai?

Jangan Coba Test Semua Sekaligus

Ini jalan ke burnout dan biasanya hasilnya test yang buruk. Pilih pendekatan incremental:

Strategi: Test coverage untuk code yang baru ditulis

Mulai hari ini, setiap fitur baru atau bug fix, wajib disertai test. Jangan touch kode lama dulu — cukup pastikan kode baru punya test.

Strategi: Test untuk bug yang baru ditemukan

Setiap kali ada bug yang di-report, tulis dulu failing test yang reproduce bug tersebut, baru fix. Ini memastikan bug tidak balik lagi.

Strategi: Test untuk critical path

Identifikasi 3-5 fitur paling critical di aplikasi kamu (yang kalau rusak paling menyakitkan). Tulis integration test untuk happy path-nya dulu.

Tools yang Membuat Testing Lebih Menyenangkan

JavaScript/TypeScript

  • Vitest — pengganti Jest yang jauh lebih cepat, DX yang lebih baik
  • Testing Library (React/Vue/etc) — mendorong kamu test behavior, bukan implementation
  • Playwright untuk E2E — developer experience terbaik saat ini

Python

  • pytest — bukan PyTest, tapi pytest. Jauh lebih ergonomis dari unittest bawaan Python
  • factory_boy untuk generate test data yang realistic
  • httpx + pytest untuk test API endpoints

PHP / Laravel

  • PHPUnit sudah built-in di Laravel — tidak perlu setup tambahan
  • Pest — alternatif dengan syntax yang lebih expressive dan clean

AI untuk Testing: Cheat Code yang Sah

Ini tidak banyak dibicarakan tapi sangat berguna: gunakan AI untuk generate test boilerplate.

Berikan fungsi atau component kamu ke Claude/GPT, minta dia generate test cases termasuk edge cases yang mungkin tidak terpikirkan. Hasilnya sering perlu di-adjust, tapi jauh lebih cepat dari mulai dari scratch.

Workflow yang efektif:

  1. Tulis fungsi/component
  2. Copy ke AI, minta “generate comprehensive test cases for this function”
  3. Review dan adjust test yang di-generate
  4. Run, fix yang fail
  5. Commit bersama kode aslinya

Ini tidak berarti kamu malas — ini berarti kamu efisien.

Tanda-tanda Test Kamu Sudah Cukup Baik

Kamu tidak perlu 100% code coverage untuk bisa tidur nyenyak. Beberapa indikator bahwa testing kamu sudah on track:

  • Kamu percaya diri refactor kode tanpa takut ada yang rusak diam-diam
  • Kalau ada bug baru, kamu langsung tahu di mana lewat failing test
  • Test berjalan dalam hitungan detik, bukan menit
  • Nama-nama test bisa dibaca sebagai dokumentasi fitur

Punya project yang butuh ditambah test coverage, atau mau setup testing infrastructure dari awal? Hubungi mafadev — kita kerjakan bersama.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *