EMZETT.
Login

Kurz: Go bringt ein Testframework mit: Das Paket testing und der Befehl go test.

Teil des Kurses Go

Kapitel 10 von 11 im Kurs Go. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Go bringt ein Testframework mit: Das Paket testing und der Befehl go test.

Tests schreiben

Testdateien enden auf _test.go, Testfunktionen beginnen mit Test:

// mathe/mathe.go
package mathe
 
func Addiere(a, b int) int { return a + b }
 
func Teile(a, b int) (int, error) {
    if b == 0 {
        return 0, ErrDivisionDurchNull
    }
    return a / b, nil
}
 
// mathe/mathe_test.go
package mathe
 
import "testing"
 
func TestAddiere(t *testing.T) {
    if got := Addiere(2, 3); got != 5 {
        t.Errorf("Addiere(2, 3) = %d, erwartet 5", got)
    }
}
 
func TestTeileDurchNull(t *testing.T) {
    _, err := Teile(1, 0)
    if err == nil {
        t.Fatal("Fehler erwartet")
    }
}
go test ./...            # alle Tests
go test -v ./...         # ausführlich
go test -run Addiere     # nur passende Tests
go test -race ./...      # mit Datenwettlauf-Erkennung
go test -cover ./...     # Testabdeckung

Tabellengetriebene Tests

Der Go-Stil: eine Tabelle mit Fällen, durchlaufen mit Unter-Tests:

func TestIstPalindrom(t *testing.T) {
    tests := []struct {
        name     string
        eingabe  string
        erwartet bool
    }{
        {"einfach", "Anna", true},
        {"mit Leerzeichen", "Ein Esel lese nie", true},
        {"kein Palindrom", "Hallo", false},
        {"leer", "", true},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := IstPalindrom(tt.eingabe); got != tt.erwartet {
                t.Errorf("IstPalindrom(%q) = %v, erwartet %v", tt.eingabe, got, tt.erwartet)
            }
        })
    }
}

Das Prinzip als lauffähiges Programm (ohne go test):

package main
 
import (
    "fmt"
    "strings"
    "unicode"
)
 
func IstPalindrom(s string) bool {
    var r []rune
    for _, c := range strings.ToLower(s) {
        if unicode.IsLetter(c) || unicode.IsDigit(c) {
            r = append(r, c)
        }
    }
    for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
        if r[i] != r[j] {
            return false
        }
    }
    return true
}
 
func main() {
    tests := []struct {
        name     string
        eingabe  string
        erwartet bool
    }{
        {"einfach", "Anna", true},
        {"mit Leerzeichen", "Ein Esel lese nie", true},
        {"kein Palindrom", "Hallo", false},
        {"leer", "", true},
    }
    fehler := 0
    for _, tt := range tests {
        got := IstPalindrom(tt.eingabe)
        status := "OK    "
        if got != tt.erwartet {
            status = "FEHLER"
            fehler++
        }
        fmt.Printf("%s %s\n", status, tt.name)
    }
    fmt.Println(fehler, "Fehler")
}

Ausgabe:

OK     einfach
OK     mit Leerzeichen
OK     kein Palindrom
OK     leer
0 Fehler
MethodeBedeutung
t.Error, t.ErrorfFehler melden, Test läuft weiter
t.Fatal, t.FatalfFehler melden und abbrechen
t.Run(name, f)Unter-Test
t.Helper()Hilfsfunktion (bessere Zeilenangaben)
t.TempDir()temporärer Ordner
t.Parallel()parallel ausführen
t.Cleanup(f)Aufräumen

Beispiele als Dokumentation

Funktionen ExampleXyz mit // Output: werden getestet und erscheinen in der Dokumentation:

func ExampleAddiere() {
    fmt.Println(Addiere(2, 3))
    // Output: 5
}

Benchmarks und Fuzzing

func BenchmarkAddiere(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Addiere(2, 3)
    }
}
// go test -bench=. -benchmem
 
func FuzzIstPalindrom(f *testing.F) {
    f.Add("Anna")
    f.Fuzz(func(t *testing.T, s string) {
        IstPalindrom(s)                // darf nie abstürzen
    })
}
// go test -fuzz=Fuzz

Abhängigkeiten ersetzen

Dank Interfaces lässt sich vieles einfach durch Attrappen ersetzen:

package main
 
import "fmt"
 
type Speicher interface {
    Lade(schluessel string) (string, bool)
}
 
type FakeSpeicher map[string]string
 
func (f FakeSpeicher) Lade(k string) (string, bool) { v, ok := f[k]; return v, ok }
 
func Begruessung(s Speicher, id string) string {
    if name, ok := s.Lade(id); ok {
        return "Hallo " + name
    }
    return "Hallo Fremder"
}
 
func main() {
    fake := FakeSpeicher{"1": "Mia"}
    fmt.Println(Begruessung(fake, "1"), "|", Begruessung(fake, "2"))
}

Ausgabe:

Hallo Mia | Hallo Fremder

Die HTTP-Testhilfe net/http/httptest testet Server und Clients ohne Netzwerk.

Werkzeuge der Go-Welt

WerkzeugZweck
go fmt / gofmteinheitliche Formatierung (keine Diskussionen!)
go vetfindet verdächtigen Code (falsche Formatverben, unerreichbarer Code …)
staticcheck, golangci-lintweitere Analyse
go doc, pkg.go.devDokumentation
goplsSprachserver für den Editor
dlv (Delve)Debugger
pprofProfiling (CPU, Speicher)
go build -ldflagsVersionsinfo einbauen
GOOS=linux GOARCH=amd64 go buildCross-Compiling für andere Systeme

Mit Cross-Compiling erzeugst du von deinem Rechner aus Programme für Linux, Windows, macOS und ARM, ohne Zusatzwerkzeuge.

Merke

  • Tests: Datei x_test.go, Funktion TestXyz(t *testing.T), Start mit go test ./...
  • Tabellengetriebene Tests mit t.Run sind der Standard
  • -race und -cover bei jedem Testlauf
  • Interfaces erleichtern Attrappen (Fakes)
  • go fmt, go vet, golangci-lint halten den Code sauber

Übungsaufgabe

Schreibe eine Funktion FizzBuzz(n int) string mit einer Tabelle von Testfällen (1, 3, 5, 15, 7).

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Pakete, Module und Standardbibliothek

Weiter: Nachschlagen: Spickzettel

Alle Kapitel: Go im Überblick