Test Doubles

Saat membangun perangkat lunak, unit testing adalah pilar utama untuk memastikan kualitas dan keandalan kode. Namun, sering kali kita menghadapi tantangan: bagaimana cara menguji sebuah unit (misalnya, sebuah fungsi atau method) secara terisolasi jika ia memiliki dependensi ke komponen lain seperti database, layanan eksternal (API), atau sistem file?

Di sinilah Test Doubles berperan. Mereka adalah "aktor pengganti" dalam dunia pengujian yang memungkinkan kita mengisolasi System Under Test (SUT) dari dependensinya, sehingga pengujian menjadi lebih cepat, dapat diandalkan, dan fokus pada logika yang sedang diuji.

Artikel ini akan membahas secara mendalam apa itu Test Doubles, mengapa kita membutuhkannya, dan akan mengupas tuntas lima jenis utamanya—Dummy, Fake, Stub, Spy, dan Mock—lengkap dengan contoh implementasi praktis menggunakan Go.


Apa itu Test Doubles?

Istilah "Test Double" dipopulerkan oleh Gerard Meszaros dalam bukunya xUnit Test Patterns. Analogi sederhananya adalah seperti stunt double (pemeran pengganti) dalam produksi film. Ketika sebuah adegan terlalu berbahaya atau memerlukan keahlian khusus, mereka menggunakan stunt double untuk menggantikan aktor utama.

Dalam pengujian perangkat lunak, Test Double adalah objek apa pun yang berpura-pura menjadi objek dependensi asli untuk tujuan pengujian. Tujuannya adalah untuk menggantikan dependensi yang nyata dengan versi yang bisa kita kontrol sepenuhnya.

Mengapa kita membutuhkan Test Doubles?

  1. Isolasi: Memastikan bahwa kegagalan tes benar-benar disebabkan oleh kesalahan pada unit yang diuji (SUT), bukan karena masalah pada dependensinya (misalnya, koneksi database terputus).

  2. Kecepatan: Menghindari panggilan yang lambat ke jaringan, database, atau sistem file. Tes unit harus berjalan dalam hitungan milidetik.

  3. Determinisme: Menghilangkan ketidakpastian. API eksternal bisa saja down atau mengembalikan data yang berbeda-beda. Dengan Test Double, kita bisa memastikan dependensi selalu mengembalikan respons yang kita inginkan.

  4. Simulasi Skenario Sulit: Memudahkan simulasi kondisi yang sulit diciptakan di dunia nyata, seperti kesalahan jaringan, respons error dari API, atau kondisi race condition.


Tipe-Tipe Test Doubles

Ada lima jenis utama Test Doubles, masing-masing dengan tujuan dan kasus penggunaan yang spesifik. Mari kita bahas satu per satu.

Untuk semua contoh, kita akan menggunakan skenario sederhana: sebuah UserService yang bertugas mendaftarkan pengguna baru. Layanan ini memiliki dua dependensi:

  1. UserRepository: Untuk menyimpan data pengguna ke database.

  2. Notifier: Untuk mengirim notifikasi email selamat datang.

Berikut adalah interface dan struct yang akan kita gunakan:

Go

Sekarang, mari kita lihat bagaimana setiap jenis Test Double digunakan untuk menguji UserService.

1. Dummy Objects

Dummy adalah jenis Test Double yang paling sederhana. Objek ini dilewatkan sebagai argumen ke dalam fungsi atau method, tetapi tidak pernah benar-benar digunakan. Tujuannya hanya untuk mengisi daftar parameter agar kode bisa berjalan tanpa error.

  • Tujuan: Memenuhi "kontrak" parameter fungsi.

  • Analogi: Manekin di kursi penumpang mobil saat uji tabrak. Ia ada di sana untuk mengisi ruang, tetapi tidak ada yang peduli pada perilakunya.

Contoh di Go

Misalkan kita ingin menguji bahwa RegisterUser berhasil membuat pengguna, dan dalam skenario ini kita tidak peduli apakah notifikasi terkirim atau tidak. Kita hanya butuh objek Notifier agar NewUserService tidak error.

2. Fake Objects

Fake adalah objek yang memiliki implementasi fungsional, tetapi jauh lebih sederhana daripada versi produksi. Fake biasanya menggantikan dependensi kompleks seperti database dengan versi in-memory.

  • Tujuan: Menyediakan implementasi ringan yang berfungsi untuk pengujian.

  • Analogi: Simulator penerbangan. Ia meniru perilaku pesawat sungguhan tetapi dalam lingkungan yang terkendali dan aman.

Contoh di Go

Kita akan membuat FakeUserRepository yang menggunakan map di memori untuk menyimpan pengguna, meniru perilaku database.

3. Stubs

Stub menyediakan jawaban yang sudah ditentukan (kalengan) untuk panggilan selama pengujian. Stub digunakan ketika kita ingin menguji bagaimana SUT bereaksi terhadap data atau kondisi tertentu dari dependensinya. Ini berfokus pada verifikasi state.

  • Tujuan: Memberikan input terkontrol ke SUT.

  • Analogi: Seorang aktor yang hanya diberi skrip untuk satu kalimat dan akan selalu mengucapkan kalimat itu setiap kali ditanya.

Contoh di Go

Kita ingin menguji skenario di mana pendaftaran pengguna gagal karena database error. Kita akan membuat StubUserRepository yang selalu mengembalikan error saat Save dipanggil.

Go

4. Spies

Spy adalah Stub yang juga merekam informasi tentang bagaimana ia dipanggil. Ini memungkinkan kita untuk memverifikasi interaksi antara SUT dan dependensinya. Spy berfokus pada verifikasi perilaku.

  • Tujuan: Memata-matai SUT untuk memastikan ia memanggil dependensi dengan benar.

  • Analogi: Seorang mata-mata yang tidak hanya memberikan informasi (seperti Stub), tetapi juga mencatat siapa yang bertanya, kapan, dan dengan pertanyaan apa.

Contoh di Go

Kita ingin memastikan bahwa setelah pengguna berhasil disimpan, method SendWelcomeEmail pada Notifier benar-benar dipanggil dengan email yang benar.

5. Mocks

Mock adalah objek yang paling "cerdas". Mocks diprogram dengan ekspektasi-yaitu, spesifikasi panggilan metode yang diharapkan akan diterima. Jika SUT tidak berinteraksi dengan mock persis seperti yang diharapkan, tes akan gagal. Mock juga berfokus pada verifikasi perilaku, tetapi dengan cara yang lebih ketat daripada Spy.

  • Tujuan: Mendefinisikan ekspektasi interaksi yang ketat dan secara otomatis memverifikasinya.

  • Analogi: Seorang instruktur militer yang memberikan perintah spesifik kepada seorang kadet. Jika kadet tidak melakukan persis seperti yang diperintahkan, ia akan langsung gagal.

Membuat mock secara manual bisa sangat merepotkan. Di Go, sangat umum menggunakan library seperti testify/mock atau gomock untuk ini.

Contoh di Go (menggunakan testify/mock)

Pertama, install library-nya:

go get github.com/stretchr/testify/mock

Kemudian, kita buat mock-nya.

Sekarang, kita gunakan mock ini dalam tes untuk memverifikasi interaksi secara ketat.


Kesimpulan: Kapan Menggunakan yang Mana?

Tipe Double
Tujuan Utama
Verifikasi
Kapan Digunakan

Dummy

Mengisi parameter

-

Ketika sebuah argumen diperlukan tapi tidak akan digunakan dalam jalur eksekusi tes.

Fake

Simulasi fungsionalitas

State

Ketika butuh dependensi yang berfungsi tapi versi ringan (misal: database in-memory).

Stub

Menyediakan data "kalengan"

State

Ketika ingin menguji bagaimana SUT bereaksi terhadap input atau kondisi tertentu.

Spy

Merekam interaksi

Behavior

Ketika ingin memeriksa apakah sebuah metode dipanggil dan dengan apa tanpa memaksakan urutan yang ketat.

Mock

Mendefinisikan ekspektasi

Behavior

Ketika ingin memverifikasi protokol interaksi yang spesifik dan ketat antara SUT dan dependensinya.

Menggunakan Test Doubles adalah keterampilan fundamental dalam unit testing modern. Dengan memahami perbedaan di antara kelima jenis ini, Anda dapat menulis tes yang lebih bersih, lebih cepat, dan lebih terfokus, yang pada akhirnya akan menghasilkan kode yang lebih kuat dan mudah dipelihara. Selamat menguji!

Last updated