Pelajaran modul (2/5)
Tes berdasarkan tabel
Tes berdasarkan tabel adalah pola idiomatis Go: alih-alih menulis N fungsi TestThisSpecificCase yang terpisah, Anda mendeklarasikan sepotong kasus dan mengulanginya. Ada SATU blok pernyataan, dan data pengujian dipisahkan dari logika.
Pola dasar
func TestAbs(t *testing.T) {
cases := []struct {
name string
in int
want int
}{
{"pos", 3, 3},
{"neg", -3, 3},
{"zero", 0, 0},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := Abs(tc.in); got != tc.want {
t.Errorf("Abs(%d) = %d; voglio %d", tc.in, got, tc.want)
}
})
}
}Tiga bahan:
- Sepotong struktur anonim dengan kolom biasa:
name, masukan, keluaran yang diharapkan (dan mungkin kesalahan yang diharapkan). for _, tc := range cases— loop eksekusi.t.Run(tc.name, func(t *testing.T) { ... })— setiap kasus menjadi subtes dengan namanya sendiri.
Mengapa t.Run?
t.Run membuat subtes yang memiliki:
- Nama individu ditampilkan di output:
TestAbs/pos,TestAbs/neg, ... t *testing.Tmiliknya sendiri: kegagalan dalam satu kasus tidak menghentikan kasus lainnya (kecualit.Fataldigunakan).- Pemfilteran baris perintah:
go test -run TestAbs/neghanya menjalankan kasus tersebut. - Paralelisme opsional: panggil
t.Parallel()di dalam fungsi untuk memparalelkan.
go test -v -run TestAbs/negKasus dengan kesalahan yang diharapkan
cases := []struct {
name string
in string
want int
wantErr bool
}{
{"ok", "42", 42, false},
{"bad", "x", 0, true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := Atoi(tc.in)
if (err != nil) != tc.wantErr {
t.Fatalf("err = %v, wantErr = %v", err, tc.wantErr)
}
if !tc.wantErr && got != tc.want {
t.Errorf("got %d, want %d", got, tc.want)
}
})
}Jebakan penutupan loop (sebelum Go 1.22)
Dalam versi sebelum Go 1.22, variabel tc dibagikan di seluruh iterasi: memanggil t.Parallel() di dalam t.Run menyebabkan semua subtes dijalankan terhadap kasus terakhir yang sama. Perbaikannya adalah:
for _, tc := range cases {
tc := tc // local rebind (pre-1.22)
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// ...
})
}Memberi nama pada kasus-kasus tersebut
Praktik terbaik:
- Nama pendek namun menggugah:
"empty input","too large","negative". - Tidak ada spasi jika ingin memfilter dengan mudah menggunakan
-run(atau menggunakan garis bawah). - Selalu sertakan kasus "happy path", kasus "edge" (nol, kosong) dan setidaknya satu kasus kesalahan.
Latihan
Tentukan potongan kasus dari struct anonim dengan bidang masuk dan inginkan, diisi dengan 3 kasus untuk menguji Abs (positif, negatif, nol).
Solusi tersedia setelah 3 upaya
Tambahkan t.Run dengan tc.name untuk mengubah setiap kasus menjadi subtes bernama.
Solusi tersedia setelah 3 upaya
Apa keuntungan utama dari pola berbasis tabel dibandingkan N fungsi Tes terpisah?
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { ... })
}