Lompati ke konten utama
Modul 9 · Pelajaran 2 dari 542/50 dalam kursus~14 min
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

Go
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:

  1. Sepotong struktur anonim dengan kolom biasa: name, masukan, keluaran yang diharapkan (dan mungkin kesalahan yang diharapkan).
  2. for _, tc := range cases — loop eksekusi.
  3. 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.T miliknya sendiri: kegagalan dalam satu kasus tidak menghentikan kasus lainnya (kecuali t.Fatal digunakan).
  • Pemfilteran baris perintah: go test -run TestAbs/neg hanya menjalankan kasus tersebut.
  • Paralelisme opsional: panggil t.Parallel() di dalam fungsi untuk memparalelkan.
Bash
go test -v -run TestAbs/neg

Kasus dengan kesalahan yang diharapkan

Go
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:

Go
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

Latihan#go.m9.l2.e1
Upaya: 0Memuat…

Tentukan potongan kasus dari struct anonim dengan bidang masuk dan inginkan, diisi dengan 3 kasus untuk menguji Abs (positif, negatif, nol).

Memuat editor…

Solusi tersedia setelah 3 upaya

Latihan#go.m9.l2.e2
Upaya: 0Memuat…

Tambahkan t.Run dengan tc.name untuk mengubah setiap kasus menjadi subtes bernama.

Memuat editor…

Solusi tersedia setelah 3 upaya

Quiz#go.m9.l2.e3
Siap

Apa keuntungan utama dari pola berbasis tabel dibandingkan N fungsi Tes terpisah?

Go
for _, tc := range cases {
  t.Run(tc.name, func(t *testing.T) { ... })
}
Pilihan jawaban