Semakin banyak test kita tulis, semakin banyak pula kode yang berulang: membandingkan dua nilai, memastikan sebuah error muncul, menyiapkan berkas kerja. Test helper adalah fungsi bantu yang menampung pengulangan tersebut agar logika pemeriksaan ditulis satu kali. Helper hanyalah fungsi biasa, tetapi package testing menyiapkan tiga alat agar helper berperilaku benar: t.Helper menjaga nomor baris pelaporan, parameter *testing.T memindahkan kekuatan menandai kegagalan ke dalam helper, dan t.Cleanup menjadwalkan pembersihan. Pada chapter ini kita membangun helper untuk parser durasi sederhana dan membuktikan ketiganya lewat output asli.
Kode Produksi dan Galat Sentinel #
Kode yang diuji adalah fungsi ParseMenit: mengubah teks menjadi jumlah menit, menolak non-angka, dan menolak angka negatif lewat galat sentinel ErrNegatif:
package main
import (
"errors"
"fmt"
"strconv"
)
// ErrNegatif menandai input menit yang bernilai negatif.
var ErrNegatif = errors.New("nilai menit negatif")
// ParseMenit mengubah teks menjadi jumlah menit. Input yang bukan
// angka ditolak, begitu pula angka negatif.
func ParseMenit(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("parse %q: %w", s, err)
}
if n < 0 {
return 0, fmt.Errorf("%d: %w", n, ErrNegatif)
}
return n, nil
}Sentinel ErrNegatif memungkinkan pemanggil memeriksa jenis kegagalan dengan errors.Is — pola dari chapter errors.Is dan errors.As
.
Helper yang Menandai Kegagalan #
Berkas konversi_test.go berisi dua helper — HarusSama dan HarusError — serta empat test yang memakainya:
package main
import (
"errors"
"strconv"
"testing"
)
// HarusSama membandingkan got dengan want dan melaporkan kegagalan
// pada baris pemanggil helper, bukan pada baris di dalam helper.
func HarusSama(t *testing.T, got, want int) {
t.Helper()
if got != want {
t.Errorf("got %d; want %d", got, want)
}
}
// HarusError memeriksa bahwa err tidak nil dan galatnya cocok dengan
// target menurut errors.Is.
func HarusError(t *testing.T, err, target error) {
t.Helper()
if err == nil {
t.Fatalf("got nil; want %v", target)
}
if !errors.Is(err, target) {
t.Errorf("got %v; want %v", err, target)
}
}
func TestParseMenit(t *testing.T) {
got, err := ParseMenit("90")
if err != nil {
t.Fatalf("ParseMenit(\"90\") galat tak terduga: %v", err)
}
HarusSama(t, got, 90)
}
func TestParseMenitBukanAngka(t *testing.T) {
_, err := ParseMenit("sembilan")
HarusError(t, err, strconv.ErrSyntax)
}
func TestParseMenitNegatif(t *testing.T) {
_, err := ParseMenit("-5")
HarusError(t, err, ErrNegatif)
}
// TestUrutanPembersihan memperlihatkan urutan t.Cleanup: yang
// didaftarkan terakhir dijalankan lebih dulu (LIFO).
func TestUrutanPembersihan(t *testing.T) {
t.Log("awal test")
t.Cleanup(func() { t.Log("pembersihan pertama") })
t.Log("tengah test")
t.Cleanup(func() { t.Log("pembersihan kedua") })
t.Cleanup(func() { t.Log("pembersihan ketiga") })
t.Log("akhir test")
}Mari kita telaah baris demi baris berkas test di atas:
- Baris 11-16 (
HarusSama): helper pemeriksa nilai. Helper menerimat *testing.Tsehingga helper dapat memanggilt.Errorfatas nama test pemanggil. Pemanggilan cukup satu baris —HarusSama(t, got, 90)— menggantikan lima barisiflengkap. - Baris 12 (
t.Helper()): menandai fungsi ini sebagai helper. Efeknya: nomor baris pada pesan kegagalan menunjuk ke pemanggil helper, bukan ke barist.Errorfdi dalam helper. Pembuktiannya ada di bawah. - Baris 20-28 (
HarusError): helper pemeriksa galat. Baris 23 memakait.Fatalfketikaerrbernilainil— pemeriksaanerrors.Isdi baris 25 tidak bermakna tanpa galat, jadi eksekusi dihentikan. - Baris 30-36 (
TestParseMenit): test jalur sukses:ParseMenit("90")harus mengembalikan90; baris 33 memakait.Fatalfuntuk kegagalan tak terduga dan baris 35 memanggilHarusSama. - Baris 38-46 (dua test kegagalan):
TestParseMenitBukanAngkamengharapkanstrconv.ErrSyntaxdari masukan"sembilan",TestParseMenitNegatifmengharapkan sentinelErrNegatifdari masukan"-5"— keduanya cukup memanggilHarusErrorsatu kali. - Baris 50-57 (
TestUrutanPembersihan): test khusus yang memperagakan urutant.Cleanupdengant.Log; kita bedah hasilnya pada bagian t.Cleanup.
Jalankan seluruh test dari direktori chapter:
go test -v ./...Output yang Dihasilkan:
=== RUN TestParseMenit
--- PASS: TestParseMenit (0.00s)
=== RUN TestParseMenitBukanAngka
--- PASS: TestParseMenitBukanAngka (0.00s)
=== RUN TestParseMenitNegatif
--- PASS: TestParseMenitNegatif (0.00s)
=== RUN TestUrutanPembersihan
konversi_test.go:51: awal test
konversi_test.go:53: tengah test
konversi_test.go:56: akhir test
konversi_test.go:55: pembersihan ketiga
konversi_test.go:54: pembersihan kedua
konversi_test.go:52: pembersihan pertama
--- PASS: TestUrutanPembersihan (0.00s)
PASSEmpat test lulus. Enam baris konversi_test.go:NN: pada TestUrutanPembersihan adalah jejak t.Log: tiga baris pertama dicetak oleh isi test, lalu tiga baris pembersihan muncul terbalik dari urutan pendaftaran.
t.Cleanup: Pembersihan dengan Urutan Terbalik #
t.Cleanup(func()) mendaftarkan fungsi yang dijalankan setelah test selesai dengan urutan LIFO (last in, first out): pendaftaran ketiga di baris 55 dijalankan paling awal, pendaftaran pertama di baris 52 dijalankan paling akhir — persis seperti defer: resource yang dibuka terakhir dibersihkan lebih dulu.
defer di dalam fungsi test juga bekerja, tetapi t.Cleanup unggul saat pembersihan didaftarkan dari helper: defer di dalam helper terlaksana saat helper kembali — terlalu dini — sedangkan t.Cleanup menunggu test pemanggilnya selesai. Pola umumnya:
func buatBerkasUji(t *testing.T) string {
t.Helper()
nama := filepath.Join(t.TempDir(), "data.txt")
// ... tulis berkas ...
t.Cleanup(func() { os.Remove(nama) })
return nama
}t.Helper: Kegagalan Dilaporkan di Baris Pemanggil #
Eksperimen kecil membuktikan kekuatan t.Helper. Ubah sementara HarusSama(t, got, 90) pada baris 35 menjadi HarusSama(t, got, 91), lalu jalankan hanya test tersebut:
go test -v -run "^TestParseMenit$" ./...Output yang Dihasilkan:
=== RUN TestParseMenit
konversi_test.go:35: got 90; want 91
--- FAIL: TestParseMenit (0.00s)
FAILNomor barisnya 35 — baris pemanggilan HarusSama di TestParseMenit. Sekarang matikan t.Helper() dengan mengubah baris 12 menjadi _ = t (baris lain tidak disentuh agar nomor tetap), lalu jalankan perintah yang sama:
Output yang Dihasilkan:
=== RUN TestParseMenit
konversi_test.go:14: got 90; want 91
--- FAIL: TestParseMenit (0.00s)
FAILNomor baris bergeser ke 14 — baris t.Errorf di dalam HarusSama. Tanpa t.Helper, pembaca output diarahkan ke dalam helper; dengan t.Helper, pembaca langsung diarahkan ke test yang benar-benar bermasalah. Kembalikan kedua perubahan setelah selesai.
Menerima *testing.T atau Mengembalikan error #
Helper seperti HarusSama menerima t *testing.T dan menandai kegagalan sendiri — cocok untuk pemeriksaan test murni. Helper setup boleh memilih rasa mana pun, sedangkan fungsi yang juga dipakai kode produksi sebaiknya mengembalikan error agar tetap bisa dipanggil program nyata.
Komunitas Go menyebut dua gaya penandaan kegagalan sebagai assert dan require: assert melanjutkan eksekusi (t.Errorf), require menghentikan eksekusi (t.Fatalf). Library pihak ketiga testify memaketkan keduanya dengan nama assert dan require, tetapi standard library sudah mencukupi — helper sederhana di atas memberi hasil setara tanpa dependensi.
| Keputusan | Bentuk helper | Alasan |
|---|---|---|
| Pemeriksaan hasil test | func HarusSama(t *testing.T, got, want int) | Kegagalan langsung dilaporkan dengan konteks test |
| Setup berkas/data | func buatBerkasUji(t *testing.T) string | Bisa memanggil t.Cleanup, lalu mengembalikan data siap pakai |
| Logika yang juga dipakai produksi | func MuatKonfigurasi(path string) (*Config, error) | Pemanggil produksi dan test sama-sama menangani galat |
Praktik sehat helper dirangkum pada tabel berikut:
| Lakukan | Hindari |
|---|---|
Panggil t.Helper() pada baris pertama helper penanda kegagalan | Membiarkan nomor baris kegagalan menunjuk ke dalam helper |
| Kembalikan data siap pakai dari helper setup | Menyimpan status di variabel global yang dipakai bergantian antar test |
Daftarkan pembersihan lewat t.Cleanup | Mengandalkan defer di dalam helper untuk membersihkan resource test |
| Pecah helper yang memuat lebih dari satu tanggung jawab | Menumpuk banyak pemeriksaan tidak berhubungan dalam satu helper |
Dengan helper, pemeriksaan berulang sudah tertangani. Chapter berikutnya naik satu tingkat ke test fixture: data uji dan lingkungan sementara — direktori, berkas, sistem berkas palsu — yang disiapkan sebelum test berjalan dan bersih otomatis setelahnya.
Referensi #
- Dokumentasi testing.T.Helper — spesifikasi resmi penandaan helper.
- Dokumentasi testing.T.Cleanup — spesifikasi resmi pendaftaran pembersihan dan urutannya.
- Dokumentasi package testing
— referensi resmi seluruh metode
testing.T.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
konversi_test.go
Unduh berkaspackage main
import (
"errors"
"strconv"
"testing"
)
// HarusSama membandingkan got dengan want dan melaporkan kegagalan
// pada baris pemanggil helper, bukan pada baris di dalam helper.
func HarusSama(t *testing.T, got, want int) {
t.Helper()
if got != want {
t.Errorf("got %d; want %d", got, want)
}
}
// HarusError memeriksa bahwa err tidak nil dan galatnya cocok dengan
// target menurut errors.Is.
func HarusError(t *testing.T, err, target error) {
t.Helper()
if err == nil {
t.Fatalf("got nil; want %v", target)
}
if !errors.Is(err, target) {
t.Errorf("got %v; want %v", err, target)
}
}
func TestParseMenit(t *testing.T) {
got, err := ParseMenit("90")
if err != nil {
t.Fatalf("ParseMenit(\"90\") galat tak terduga: %v", err)
}
HarusSama(t, got, 90)
}
func TestParseMenitBukanAngka(t *testing.T) {
_, err := ParseMenit("sembilan")
HarusError(t, err, strconv.ErrSyntax)
}
func TestParseMenitNegatif(t *testing.T) {
_, err := ParseMenit("-5")
HarusError(t, err, ErrNegatif)
}
// TestUrutanPembersihan memperlihatkan urutan t.Cleanup: yang
// didaftarkan terakhir dijalankan lebih dulu (LIFO).
func TestUrutanPembersihan(t *testing.T) {
t.Log("awal test")
t.Cleanup(func() { t.Log("pembersihan pertama") })
t.Log("tengah test")
t.Cleanup(func() { t.Log("pembersihan kedua") })
t.Cleanup(func() { t.Log("pembersihan ketiga") })
t.Log("akhir test")
}
konversi.go
Unduh berkaspackage main
import (
"errors"
"fmt"
"strconv"
)
// ErrNegatif menandai input menit yang bernilai negatif.
var ErrNegatif = errors.New("nilai menit negatif")
// ParseMenit mengubah teks menjadi jumlah menit. Input yang bukan
// angka ditolak, begitu pula angka negatif.
func ParseMenit(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("parse %q: %w", s, err)
}
if n < 0 {
return 0, fmt.Errorf("%d: %w", n, ErrNegatif)
}
return n, nil
}