Salt la conținutul principal
Modulul 9 · Lecția 2 din 542/50 în curs~14 min
Lecții din modul (2/5)

Teste bazate pe tabel

Testele bazate pe tabel sunt modelul idiomatic al lui Go: în loc să scrieți N funcții TestThisSpecificCase separate, declarați o porțiune de cazuri și repeți. Există UN bloc de afirmații, iar datele de testare sunt separate de logică.

Modelul de bază

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)
            }
        })
    }
}

Trei ingrediente:

  1. Fețiune de structură anonimă cu câmpurile obișnuite: name, intrările, rezultatul așteptat (și poate o eroare așteptată).
  2. for _, tc := range cases — bucla de execuție.
  3. t.Run(tc.name, func(t *testing.T) { ... }) — fiecare caz devine un subtest cu propriul nume.

De ce t.Run?

t.Run creează un subtest care are:

  • Numele individual afișat în rezultat: TestAbs/pos, TestAbs/neg, ...
  • Propriul t *testing.T: un eșec într-un caz nu le oprește pe celelalte (cu excepția cazului în care se folosește t.Fatal).
  • Filtrarea liniei de comandă: go test -run TestAbs/neg rulează numai acel caz.
  • Paralelism opțional: apelați t.Parallel() în interiorul funcției pentru a paraleliza.
Bash
go test -v -run TestAbs/neg

Cazuri cu erori așteptate

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)
        }
    })
}

Capcana închiderii buclei (pre-Go 1.22)

În versiunile anterioare Go 1.22, variabila tc a fost partajată de-a lungul iterațiilor: apelarea t.Parallel() în interiorul t.Run a făcut ca toate testele secundare să ruleze în același ultim caz. Remedierea a fost:

Go
for _, tc := range cases {
    tc := tc // local rebind (pre-1.22)
    t.Run(tc.name, func(t *testing.T) {
        t.Parallel()
        // ...
    })
}

Numirea cazurilor

Cele mai bune practici:

  • Un nume scurt, dar evocator: "empty input", "too large", "negative".
  • Fără spații dacă doriți să filtrați ușor cu -run (sau să folosiți caractere de subliniere).
  • Includeți întotdeauna un caz „cale fericită”, un caz „margine” (zero, gol) și cel puțin un caz de eroare.

Exerciții

Exercițiu#go.m9.l2.e1
Încercări: 0Se încarcă…

Definiți o porțiune de cazuri din structura anonimă cu câmpuri în și dorit, populată cu 3 cazuri pentru a testa Abs (pozitiv, negativ, zero).

Se încarcă editorul...

Soluție disponibilă după 3 încercări

Exercițiu#go.m9.l2.e2
Încercări: 0Se încarcă…

Adăugați t.Run cu tc.name pentru a transforma fiecare caz într-un subtest numit.

Se încarcă editorul...

Soluție disponibilă după 3 încercări

Quiz#go.m9.l2.e3
Gata

Care este principalul avantaj al modelului bazat pe tabel față de N funcții de testare separate?

Go
for _, tc := range cases {
  t.Run(tc.name, func(t *testing.T) { ... })
}
Opțiuni de răspuns