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
Pendahuluan singkat
Perbedaan konsep: behavior vs state
Kapan menggunakan masing-masing gaya
Contoh kasus:
UserServiceyang membuat user dan mengirim notifikasiStruktur proyek contoh
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
Kelebihan & kekurangan tiap gaya
Best practices & tips untuk Go
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:
UserServicememiliki methodCreateUser(email, name):Menyimpan user ke repository (
UserRepository).Mengirim email notifikasi lewat
Notifierinterface.
Kita akan menulis 2 jenis test untuk CreateUser:
London test: verifikasi bahwa
Notifier.Senddipanggil 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/mockpada 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(...)danAssertExpectations).Tes akan gagal jika
Notifier.Sendtidak 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
Gunakan interface agar mudah mengganti implementasi nyata dengan mock/fake.
Keep tests deterministic: hindari ketergantungan waktu / random tanpa kontrol (pakai injection clock jika perlu).
Jangan mock everything: mock apa yang penting untuk behavior verification.
Prefer small, focused tests: tiap test memverifikasi satu aspek (single responsibility).
Jika memakai mock lib (testify/gomock), jangan lupa maintain expectations secara jelas; gunakan
defer mock.AssertExpectations(t)saat cocok.Buat in-memory/fake implementations untuk integration-like tests (Detroit style) supaya cek state lebih mudah.
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