Tutorial Unit Test Go: Panduan Table-Driven Tests untuk Pemula
Menulis unit test di Go secara manual dengan fungsi Test* terpisah untuk setiap skenario dengan cepat membuat berkas pengujian membengkak dan sulit dirawat. Komunitas Go mengatasi masalah repitisi ini dengan menerapkan pola table-driven tests, di mana seluruh data uji disusun rapi dalam tabel struct slice. Artikel ini membimbing Anda memahami struktur test file Go, menyusun table-driven tests dari nol, serta membaca output go test -v dengan tepat.
Ringkasan
- Berkas unit test Go menggunakan akhiran
_test.godalam paket yang sama. - Fungsi pengujian wajib diawali kata
Testdengan parametert *testing.T. - Pola table-driven tests mengelompokkan input dan skenario ke dalam tabel struct.
- Fungsi
t.Runmengeksekusi tiap baris tabel sebagai subtest mandiri. - Perintah
go test -vmenampilkan detail status PASS atau FAIL per skenario.
Langkah 1: Memahami Konvensi Berkas dan Struktur Fungsi Test Go
Langkah ini bertujuan memahami aturan penamaan berkas, struktur paket, dan penandatanganan fungsi unit test standar di Go.
Bahasa pemrograman Go menyediakan perkakas pengujian bawaan melalui perintah go test tanpa memerlukan pustaka (framework) pihak ketiga. Agar perkakas ini mengenali dan mengeksekusi pengujian secara otomatis, Anda harus mengikuti beberapa aturan penamaan dan konvensi struktur yang telah ditentukan oleh bahasa Go.
- Pahami aturan penamaan berkas test. Berkas pengujian Go harus menggunakan ekstensi
_test.godi akhir nama berkasnya, contohnyadiskon_test.go. Menurut dokumentasi paket testing Go, perkakasgo testsecara khusus memindai berkas dengan akhiran ini dan mengabaikannya saat proses kompilasi executable produksi. - Tentukan nama paket pengujian. Pada sebagian besar kasus, tempatkan berkas test dalam paket yang sama dengan kode yang diuji (
package diskon). Pendekatan ini memungkinkan pengujian mengakses fungsi internal maupun variabel privat (white-box testing). - Format penulisan fungsi pengujian. Fungsi test harus diawali dengan awalan
Testyang dilanjutkan dengan nama fungsi berawalan huruf kapital, sepertiTestHitungDiskon. Fungsi tersebut menerima satu-satunya parameter berupa penunjuk tipe*testing.T.
Tabel berikut merangkum konvensi dasar komponen pengujian beserta lingkungan yang teruji dalam tutorial ini:
| Komponen / Lingkungan | Format / Spesifikasi | Fungsi dan Peran Utama |
|---|---|---|
| Versi Go Teruji | Go 1.22+ (Teruji pada Go 1.27.1) | Runtime Go standar tanpa dependensi eksternal |
| Nama Berkas Test | <nama_file>_test.go | Memisahkan kode pengujian dari binary aplikasi utama |
| Nama Fungsi Test | func TestXxx(t *testing.T) | Titik masuk (entrypoint) eksekusi test oleh perkakas go test |
| Nama Paket | package <nama_paket> | Mengakses fungsi dan tipe internal dalam paket yang sama |
| Parameter Utama | t *testing.T | Mengontrol jalannya test, membuat log, dan melaporkan kegagalan |
| Perintah Eksekusi | go test -v | Menjalankan seluruh tes dalam paket dengan luaran rinci |
Nama fungsi setelah awalan
Testharus dimulai dengan huruf kapital. Sebagai contoh,TestHitungDiskonvalid untuk dijalankan olehgo test, sedangkanTesthitungDiskon(huruf ‘h’ kecil) akan diabaikan oleh perkakas pengujian Go.
Langkah 2: Membuat Fungsi Sederhana yang Akan Diuji
Langkah ini menyiapkan logika bisnis berupa fungsi kalkulasi diskon yang akan diuji menggunakan unit test.
Sebelum menyusun pengujian, Anda memerlukan sebuah fungsi sederhana yang memiliki masukan (input), keluaran (output), serta kondisi galat (error). Kita akan membuat fungsi perhitungan harga akhir setelah dipotong diskon belanja.
- Buat berkas logika utama
diskon.go. Siapkan logika bisnis fungsiHitungDiskonyang menerima parameter harga awal dan persentase diskon. Fungsi ini mengembalikan nilai harga setelah diskon atau objekerrorjika masukan tidak valid.
Berikut adalah implementasi lengkap kode program dalam berkas diskon.go:
package diskon
import "errors"
// HitungDiskon menghitung harga akhir setelah dipotong persentase diskon.
// Persentase diskon harus berada pada rentang 0 hingga 100.
func HitungDiskon(harga int, persentase int) (int, error) {
if harga < 0 {
return 0, errors.New("harga tidak boleh negatif")
}
if persentase < 0 || persentase > 100 {
return 0, errors.New("persentase diskon harus antara 0 dan 100")
}
potongan := (harga * persentase) / 100
return harga - potongan, nil
}- Identifikasi skenario kasus uji. Sebelum menulis test, daftarkan kemungkinan kombinasi masukan yang perlu diuji:
- Diskon bernilai normal (misalnya diskon 10% untuk harga 100.000).
- Diskon 0% (harga tidak berubah).
- Diskon 100% (harga akhir menjadi 0).
- Input harga negatif (menghasilkan error).
- Input persentase melebihi 100% (menghasilkan error).
Fungsi di Go umumnya mengembalikan dua nilai ketika ada kemungkinan kegagalan: hasil perhitungan dan
error. Desain ini sangat memudahkan pengujian kondisi happy path maupun edge case.
Langkah 3: Menyusun Table-Driven Test Menggunakan Struct Slice
Langkah ini menyusun berkas pengujian menggunakan pola table-driven test agar seluruh skenario pengujian terstruktur dalam satu tabel data.
Daripada menuliskan lima fungsi pengujian terpisah seperti TestDiskon10, TestDiskonZero, atau TestHargaNegatif, pola table-driven tests mengelompokkan semua kasus ke dalam sebuah slice dari anonymous struct.
- Buat berkas pengujian
diskon_test.go. Deklarasikan variabel tabeltestsberisi daftar kasus uji, lengkap dengan parameter masukan dan ekspektasi hasil. - Manfaatkan subtest dengan
t.Run. Iterasi baris tabel menggunakan perulanganfor rangedan panggilt.Run(tt.name, ...)untuk mengisolasi setiap skenario seperti dijelaskan dalam Go Wiki TableDrivenTests.
Berikut adalah isi dari berkas pengujian diskon_test.go:
package diskon
import (
"testing"
)
func TestHitungDiskon(t *testing.T) {
// Deklarasi tabel kasus uji berupa slice of struct
tests := []struct {
name string
harga int
persentase int
wantHarga int
wantErr bool
}{
{
name: "diskon 10 persen valid",
harga: 100000,
persentase: 10,
wantHarga: 90000,
wantErr: false,
},
{
name: "diskon 0 persen tanpa potongan",
harga: 50000,
persentase: 0,
wantHarga: 50000,
wantErr: false,
},
{
name: "diskon 100 persen gratis",
harga: 75000,
persentase: 100,
wantHarga: 0,
wantErr: false,
},
{
name: "harga negatif mengembalikan error",
harga: -10000,
persentase: 10,
wantHarga: 0,
wantErr: true,
},
{
name: "persentase melebihi 100 mengembalikan error",
harga: 50000,
persentase: 150,
wantHarga: 0,
wantErr: true,
},
}
// Perulangan untuk mengeksekusi setiap kasus uji dalam tabel
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotHarga, err := HitungDiskon(tt.harga, tt.persentase)
// Memeriksa status error
if (err != nil) != tt.wantErr {
t.Errorf("HitungDiskon() error = %v, wantErr %v", err, tt.wantErr)
return
}
// Memeriksa kesesuaian nilai hasil perhitungan
if gotHarga != tt.wantHarga {
t.Errorf("HitungDiskon() gotHarga = %v, want %v", gotHarga, tt.wantHarga)
}
})
}
}- Gunakan
t.Errorfalih-aliht.Fatalfuntuk pemeriksaan umum. Pemanggilant.Errorfmencatat kegagalan dan melanjutkan eksekusi subtest berikutnya, sedangkant.Fatalflangsung menghentikan subtest yang sedang berjalan sebagaimana dijelaskan pada panduan resmi Go tutorial Add a Test.
Pada versi Go sebelum 1.22, variabel iterasi dalam perulangan
fordibagikan di seluruh iterasi. Jika Anda menjalankan subtest secara paralel dengant.Parallel(), pastikan membuat salinan variabel lokal dengantt := ttdi dalam perulangan. Sejak Go 1.22 ke atas, variabel iterasi perulangan sudah memiliki cakupan (scope) per iterasi sehingga masalah tersebut telah teratasi secara otomatis.
Langkah 4: Menjalankan Pengujian dengan Perintah go test -v
Langkah ini menjelaskan cara mengeksekusi unit test melalui terminal serta fungsi dari bendera (flag) -v.
Setelah berkas pengujian siap, Anda dapat menjalankan tes menggunakan CLI bawaan Go dari direktori tempat berkas tersebut berada.
- Eksekusi perintah
go testtanpa bendera. Jalankan perintah standar di terminal untuk melakukan pengujian secara ringkas seperti diterangkan pada dokumentasi Go Code Testing.
go testKeluaran di terminal jika seluruh test berhasil:
PASS
ok contoh.com/diskon 0.004s- Jalankan
go test -vuntuk keluaran terperinci. Bendera-v(verbose) memaksa Go menampilkan status eksekusi dari setiap subtest yang didaftarkan melaluit.Run.
go test -vHasil keluaran di konsol terminal akan menampilkan struktur hierarki pengujian:
=== RUN TestHitungDiskon
=== RUN TestHitungDiskon/diskon_10_persen_valid
=== RUN TestHitungDiskon/diskon_0_persen_tanpa_potongan
=== RUN TestHitungDiskon/diskon_100_persen_gratis
=== RUN TestHitungDiskon/harga_negatif_mengembalikan_error
=== RUN TestHitungDiskon/persentase_melebihi_100_mengembalikan_error
--- PASS: TestHitungDiskon (0.00s)
--- PASS: TestHitungDiskon/diskon_10_persen_valid (0.00s)
--- PASS: TestHitungDiskon/diskon_0_persen_tanpa_potongan (0.00s)
--- PASS: TestHitungDiskon/diskon_100_persen_gratis (0.00s)
--- PASS: TestHitungDiskon/harga_negatif_mengembalikan_error (0.00s)
--- PASS: TestHitungDiskon/persentase_melebihi_100_mengembalikan_error (0.00s)
PASS
ok contoh.com/diskon 0.004sTabel berikut memuat opsi bendera perintah go test yang berguna dalam pengembangan sehari-hari:
| Perintah / Bendera | Deskripsi Fungsi | Contoh Penggunaan Terminal |
|---|---|---|
go test | Menjalankan seluruh pengujian secara ringkas | go test ./... |
go test -v | Menampilkan seluruh log nama fungsi dan subtest (verbose) | go test -v |
go test -run <pattern> | Menjalankan subtest tertentu yang cocok dengan pola regex | go test -v -run TestHitungDiskon/diskon_10 |
go test -cover | Menampilkan persentase cakupan kode (code coverage) | go test -cover |
go test -failfast | Menghentikan pengujian segera setelah kesalahan pertama ditemukan | go test -failfast |
Langkah 5: Menganalisis dan Membaca Output PASS serta FAIL
Langkah ini melatih kemampuan membaca serta menafsirkan log keluaran saat pengujian berhasil maupun saat terjadi kegagalan (FAIL).
Mampu membaca pesan kesalahan dengan cepat adalah keterampilan krusial bagi setiap pengembang Go. Format log Go dirancang agar memberikan konteks yang jelas mengenai lokasi dan penyebab kegagalan.
- Kenali arti simbol indikator eksekusi.
=== RUN: Menandakan bahwa fungsi atau subtest tertentu mulai dieksekusi.--- PASS:: Menandakan bahwa skenario uji berhasil diselesaikan tanpa ada pemanggilan fungsi pelaporan error.--- FAIL:: Menunjukkan bahwa skenario uji mengalami kegagalan akibat error yang dilaporkan oleht.Errorfataut.Fatalf.
- Simulasikan kegagalan pengujian. Sebagai latihan, ubah nilai ekspektasi
wantHargapada skenario"diskon 10 persen valid"di berkasdiskon_test.godari90000menjadi80000. - Jalankan kembali tes untuk mengamati laporan FAIL.
go test -v -run TestHitungDiskon/diskon_10_persen_validKeluaran terminal saat terjadi kesalahan:
=== RUN TestHitungDiskon
=== RUN TestHitungDiskon/diskon_10_persen_valid
diskon_test.go:56: HitungDiskon() gotHarga = 90000, want 80000
--- FAIL: TestHitungDiskon (0.00s)
--- FAIL: TestHitungDiskon/diskon_10_persen_valid (0.00s)
FAIL
exit status 1
FAIL contoh.com/diskon 0.005s- Bedah informasi laporan kesalahan. Dari laporan kegagalan di atas, kita dapat memperoleh tiga informasi kunci:
- Lokasi Berkas & Baris:
diskon_test.go:56menunjukkan nomor baris spesifik di mana pernyataant.Errorfdipicu. - Subtest yang Gagal:
TestHitungDiskon/diskon_10_persen_validmengidentifikasi baris tabel mana yang bermasalah tanpa mengganggu subtest lainnya. - Perbandingan Nilai:
gotHarga = 90000, want 80000memberikan perincian jelas antara hasil aktual dari fungsi (got) dengan ekspektasi yang diharapkan (want).
- Lokasi Berkas & Baris:
Dengan memahami pola table-driven tests dan pembacaan keluaran go test -v, Anda dapat menambahkan belasan skenario uji baru hanya dengan menambahkan baris struct baru ke dalam tabel, menjaga basis kode Go Anda tetap bersih, aman, dan mudah dirawat.
