Home / Programming Languages, Databases, and Software Development / Tutorial Unit Test …

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.go dalam paket yang sama.
  • Fungsi pengujian wajib diawali kata Test dengan parameter t *testing.T.
  • Pola table-driven tests mengelompokkan input dan skenario ke dalam tabel struct.
  • Fungsi t.Run mengeksekusi tiap baris tabel sebagai subtest mandiri.
  • Perintah go test -v menampilkan 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.go di akhir nama berkasnya, contohnya diskon_test.go. Menurut dokumentasi paket testing Go, perkakas go test secara 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 Test yang dilanjutkan dengan nama fungsi berawalan huruf kapital, seperti TestHitungDiskon. 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 / LingkunganFormat / SpesifikasiFungsi dan Peran Utama
Versi Go TerujiGo 1.22+ (Teruji pada Go 1.27.1)Runtime Go standar tanpa dependensi eksternal
Nama Berkas Test<nama_file>_test.goMemisahkan kode pengujian dari binary aplikasi utama
Nama Fungsi Testfunc TestXxx(t *testing.T)Titik masuk (entrypoint) eksekusi test oleh perkakas go test
Nama Paketpackage <nama_paket>Mengakses fungsi dan tipe internal dalam paket yang sama
Parameter Utamat *testing.TMengontrol jalannya test, membuat log, dan melaporkan kegagalan
Perintah Eksekusigo test -vMenjalankan seluruh tes dalam paket dengan luaran rinci

Nama fungsi setelah awalan Test harus dimulai dengan huruf kapital. Sebagai contoh, TestHitungDiskon valid untuk dijalankan oleh go test, sedangkan TesthitungDiskon (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 fungsi HitungDiskon yang menerima parameter harga awal dan persentase diskon. Fungsi ini mengembalikan nilai harga setelah diskon atau objek error jika masukan tidak valid.

Berikut adalah implementasi lengkap kode program dalam berkas diskon.go:

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:
    1. Diskon bernilai normal (misalnya diskon 10% untuk harga 100.000).
    2. Diskon 0% (harga tidak berubah).
    3. Diskon 100% (harga akhir menjadi 0).
    4. Input harga negatif (menghasilkan error).
    5. 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 tabel tests berisi daftar kasus uji, lengkap dengan parameter masukan dan ekspektasi hasil.
  • Manfaatkan subtest dengan t.Run. Iterasi baris tabel menggunakan perulangan for range dan panggil t.Run(tt.name, ...) untuk mengisolasi setiap skenario seperti dijelaskan dalam Go Wiki TableDrivenTests.

Berikut adalah isi dari berkas pengujian diskon_test.go:

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.Errorf alih-alih t.Fatalf untuk pemeriksaan umum. Pemanggilan t.Errorf mencatat kegagalan dan melanjutkan eksekusi subtest berikutnya, sedangkan t.Fatalf langsung 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 for dibagikan di seluruh iterasi. Jika Anda menjalankan subtest secara paralel dengan t.Parallel(), pastikan membuat salinan variabel lokal dengan tt := tt di 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 test tanpa bendera. Jalankan perintah standar di terminal untuk melakukan pengujian secara ringkas seperti diterangkan pada dokumentasi Go Code Testing.
bash
go test

Keluaran di terminal jika seluruh test berhasil:

text
PASS
ok  	contoh.com/diskon	0.004s
  • Jalankan go test -v untuk keluaran terperinci. Bendera -v (verbose) memaksa Go menampilkan status eksekusi dari setiap subtest yang didaftarkan melalui t.Run.
bash
go test -v

Hasil keluaran di konsol terminal akan menampilkan struktur hierarki pengujian:

text
=== 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.004s

Tabel berikut memuat opsi bendera perintah go test yang berguna dalam pengembangan sehari-hari:

Perintah / BenderaDeskripsi FungsiContoh Penggunaan Terminal
go testMenjalankan seluruh pengujian secara ringkasgo test ./...
go test -vMenampilkan seluruh log nama fungsi dan subtest (verbose)go test -v
go test -run <pattern>Menjalankan subtest tertentu yang cocok dengan pola regexgo test -v -run TestHitungDiskon/diskon_10
go test -coverMenampilkan persentase cakupan kode (code coverage)go test -cover
go test -failfastMenghentikan pengujian segera setelah kesalahan pertama ditemukango 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 oleh t.Errorf atau t.Fatalf.
  • Simulasikan kegagalan pengujian. Sebagai latihan, ubah nilai ekspektasi wantHarga pada skenario "diskon 10 persen valid" di berkas diskon_test.go dari 90000 menjadi 80000.
  • Jalankan kembali tes untuk mengamati laporan FAIL.
bash
go test -v -run TestHitungDiskon/diskon_10_persen_valid

Keluaran terminal saat terjadi kesalahan:

text
=== 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:
    1. Lokasi Berkas & Baris: diskon_test.go:56 menunjukkan nomor baris spesifik di mana pernyataan t.Errorf dipicu.
    2. Subtest yang Gagal: TestHitungDiskon/diskon_10_persen_valid mengidentifikasi baris tabel mana yang bermasalah tanpa mengganggu subtest lainnya.
    3. Perbandingan Nilai: gotHarga = 90000, want 80000 memberikan perincian jelas antara hasil aktual dari fungsi (got) dengan ekspektasi yang diharapkan (want).

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.