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ă
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:
- Fețiune de structură anonimă cu câmpurile obișnuite:
name, intrările, rezultatul așteptat (și poate o eroare așteptată). for _, tc := range cases— bucla de execuție.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ștet.Fatal). - Filtrarea liniei de comandă:
go test -run TestAbs/negrulează numai acel caz. - Paralelism opțional: apelați
t.Parallel()în interiorul funcției pentru a paraleliza.
go test -v -run TestAbs/negCazuri cu erori așteptate
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:
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
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).
Soluție disponibilă după 3 încercări
Adăugați t.Run cu tc.name pentru a transforma fiecare caz într-un subtest numit.
Soluție disponibilă după 3 încercări
Care este principalul avantaj al modelului bazat pe tabel față de N funcții de testare separate?
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { ... })
}