Direkt zum Hauptinhalt springen
eLearner.app
Modul 9 · Lektion 2 von 542/50 im Kurs~14 min
Lektionen des Moduls (2/5)

Datengesteuerte Tests (Table-driven)

Tabellengesteuerte Tests sind das idiomatische Muster von Go: Anstatt N separate TestThisSpecificCase-Funktionen zu schreiben, deklarieren Sie einen Teil der Fälle und iterieren. Es gibt EINEN Assertionsblock und die Testdaten sind von der Logik getrennt.

Das Grundmuster

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

Drei Zutaten:

  1. Anonymer Strukturausschnitt mit den üblichen Feldern: name, die Eingaben, die erwartete Ausgabe (und möglicherweise ein erwarteter Fehler).
  2. for _, tc := range cases – die Ausführungsschleife.
  3. t.Run(tc.name, func(t *testing.T) { ... }) – jeder Fall wird zu einem Untertest mit seinem eigenen Namen.

Warum t.Run?

t.Run erstellt einen Untertest mit:

  • Individueller Name wird in der Ausgabe angezeigt: TestAbs/pos, TestAbs/neg, ...
  • Sein eigener t *testing.T: Ein Fehler in einem Fall stoppt die anderen nicht (es sei denn, t.Fatal wird verwendet).
  • Befehlszeilenfilterung: go test -run TestAbs/neg führt nur in diesem Fall aus.
  • Optionale Parallelität: Rufen Sie t.Parallel() innerhalb der Funktion auf, um zu parallelisieren.
Bash
go test -v -run TestAbs/neg

Fälle mit erwarteten Fehlern

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

Die Loop-Closure-Falle (vor Go 1.22)

In Versionen vor Go 1.22 wurde die Variable tc über alle Iterationen hinweg gemeinsam genutzt: Der Aufruf von t.Parallel() innerhalb von t.Run führte dazu, dass alle Untertests für denselben letzten Fall ausgeführt wurden. Die Lösung war:

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

Benennung der Fälle

Best Practices:

  • Ein kurzer, aber eindrucksvoller Name: "empty input", "too large", "negative".
  • Keine Leerzeichen, wenn Sie einfach mit -run filtern möchten (oder Unterstriche verwenden).
  • Schließen Sie immer einen „Happy Path“-Fall, einen „Edge“-Fall (Null, leer) und mindestens einen Fehlerfall ein.

Übungen

Übung#go.m9.l2.e1
Versuche: 0Wird geladen…

Definieren Sie ein Cases-Slice einer anonymen Struktur mit den Feldern in und want, gefüllt mit 3 Cases zum Testen von Abs (positiv, negativ, null).

Editor wird geladen…

Lösung nach 3 Versuchen verfügbar

Übung#go.m9.l2.e2
Versuche: 0Wird geladen…

Fügen Sie t.Run mit tc.name hinzu, um jeden Fall in einen benannten Untertest umzuwandeln.

Editor wird geladen…

Lösung nach 3 Versuchen verfügbar

Quiz#go.m9.l2.e3
Bereit

Was ist der Hauptvorteil des tabellengesteuerten Musters gegenüber N separaten Testfunktionen?

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