London School vs Detroit School of TDD

Ringkasan singkat

Artikel ini menjelaskan dua gaya besar dalam Test-Driven Development (TDD): London School (mockist / behavior-based testing) dan Detroit School (classical / state-based testing). Disertakan penjelasan konsep, kelebihan & kekurangan, kapan harus memakai masing-masing pendekatan, serta contoh implementasi dan tes di Go.


Daftar Isi

  1. Pendahuluan singkat

  2. Perbedaan konsep: behavior vs state

  3. Kapan menggunakan masing-masing gaya

  4. Contoh kasus: UserService yang membuat user dan mengirim notifikasi

  5. Struktur proyek contoh

  6. Contoh implementasi — kode lengkap

    • 6.1. Contract & implementasi nyata

    • 6.2. London style (mockist) — menggunakan mock dan verification of interactions

    • 6.3. Detroit style (classical) — menggunakan fake/in-memory dan verification of state

  7. Kelebihan & kekurangan tiap gaya

  8. Best practices & tips untuk Go

  9. Kesimpulan


1. Pendahuluan singkat

TDD bukan hanya soal menulis tes sebelum kode. Ada perbedaan filosofi pada bagaimana tes ditulis:

  • London School (Mockist): Fokus pada behaviour — tes memverifikasi interaksi antara objek (apakah method tertentu dipanggil, dengan argumen tertentu). Sering memakai mock atau spy.

  • Detroit School (Classical / State-based): Fokus pada state — tes memverifikasi hasil akhir atau perubahan state (nilai return, database, file, dsb.). Minim penggunaan mock.

Keduanya valid; pilihan tergantung pada konteks, kompleksitas dependensi, dan kebutuhan maintainability.


2. Perbedaan konsep: behaviour vs state

  • Behaviour (London)

    • Tes memastikan "apa yang dilakukan" sistem terhadap dependensi.

    • Contoh: pastikan Mailer.Send(to, subject, body) dipanggil sekali dengan argumen X.

  • State (Detroit)

    • Tes memastikan "apa hasilnya" setelah kode dijalankan.

    • Contoh: pastikan entri user baru tersimpan di DB dan field activated = true.


3. Kapan menggunakan masing-masing gaya

  • London cocok ketika:

    • Anda ingin desain API/kontrak objek yang jelas.

    • Interaction correctness penting (mis. membentuk permintaan ke layanan eksternal yang sensitif).

    • Timbunan dependensi membuat integrasi nyata sulit di tests (latency, biaya, non-deterministic behavior).

  • Detroit cocok ketika:

    • Anda peduli pada hasil akhir dan side-effects yang ter-observable.

    • Sebisa mungkin hindari over-specifying implementasi internal.

    • In-memory/fake dependency mudah dibuat (mis. map-based repo).


4. Contoh kasus

Kita buat contoh domain sederhana:

  • UserService memiliki method CreateUser(email, name):

    • Menyimpan user ke repository (UserRepository).

    • Mengirim email notifikasi lewat Notifier interface.

Kita akan menulis 2 jenis test untuk CreateUser:

  • London test: verifikasi bahwa Notifier.Send dipanggil sekali dengan argumen tertentu.

  • Detroit test: verifikasi bahwa repository berisi user baru dan timestamp/flag sesuai.


5. Struktur proyek contoh


6. Contoh implementasi — kode lengkap

Semua contoh ditulis agar mudah dijalankan. Untuk dependency mock library, saya menggunakan github.com/stretchr/testify/mock pada contoh London-style. Jika tidak mau dependency eksternal, buat manual mock/spy (contoh disediakan).

6.1 Contract & implementasi nyata

6.2 London style (mockist)

Tujuan: pastikan Notifier.Send dipanggil sekali dengan argumen yang tepat, dan Repo.Save dipanggil.

Contoh menggunakan testify/mock:

Catatan:

  • Di London style kita sering memeriksa interaksi (notifier.On(...).Return(...) dan AssertExpectations).

  • Tes akan gagal jika Notifier.Send tidak dipanggil atau dipanggil dengan argumen berbeda.

Jika tidak ingin menambahkan dependency eksternal, bisa buat mock manual yang menyimpan panggilan dalam slice dan memeriksanya di akhir tes.

6.3 Detroit style (state-based)

Tujuan: verifikasi state akhir: user tersimpan di repository dan field terisi dengan benar. Kita gunakan in-memory repo (fake) dan real/simple notifier (yang hanya men-record atau noop).

Tes:

Catatan:

  • Di Detroit kita tidak memeriksa apakah Notifier.Send dipanggil. Kita hanya peduli efek yang dapat diobservasi (user tersimpan).

  • Keuntungan: tes tidak mengikat pada detail internal interaksi.


7. Kelebihan & Kekurangan

London (mockist)

    • Membuat kontrak interaksi jelas.

    • Berguna untuk menguji integrasi dengan dependensi eksternal yang sulit dijalankan di test.

    • Risiko over-specification: tes menjadi rapuh terhadap refactor (walau behaviour tetap sama).

    • Banyak mock bisa membuat tes sulit dipahami.

Detroit (state)

    • Tes lebih tahan terhadap refactor yang tidak mengubah hasil akhir.

    • Lebih mudah dibaca: memeriksa hasil akhir.

    • Sulit jika dependensi punya efek non-deterministik atau mahal (mis. network, time consuming tasks).

    • Membutuhkan fake implementations yang realistis agar tes bermakna.


8. Best practices & tips untuk Go

  1. Gunakan interface agar mudah mengganti implementasi nyata dengan mock/fake.

  2. Keep tests deterministic: hindari ketergantungan waktu / random tanpa kontrol (pakai injection clock jika perlu).

  3. Jangan mock everything: mock apa yang penting untuk behavior verification.

  4. Prefer small, focused tests: tiap test memverifikasi satu aspek (single responsibility).

  5. Jika memakai mock lib (testify/gomock), jangan lupa maintain expectations secara jelas; gunakan defer mock.AssertExpectations(t) saat cocok.

  6. Buat in-memory/fake implementations untuk integration-like tests (Detroit style) supaya cek state lebih mudah.

  7. Mix & match: tidak harus pilih satu gaya untuk seluruh codebase. Untuk bagian yang mengandung integrasi kompleks, mockist berguna; untuk logic murni, state-based seringkali lebih baik.


9. Kesimpulan

London dan Detroit adalah dua filosofi TDD yang masing-masing berguna tergantung konteks. Di Go, penggunaan interface memudahkan kedua pendekatan. Kunci utamanya: tulis tes yang membuat Anda percaya pada kode, tidak menahan Anda dari melakukan refactor yang sehat.


Last updated