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 ./... # TestabdeckungTabellengetriebene 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")
}OK einfach OK mit Leerzeichen OK kein Palindrom OK leer 0 Fehler
| Methode | Bedeutung |
|---|---|
t.Error, t.Errorf | Fehler melden, Test läuft weiter |
t.Fatal, t.Fatalf | Fehler 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=FuzzAbhä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"))
}Hallo Mia | Hallo Fremder
Die HTTP-Testhilfe net/http/httptest testet Server und Clients ohne Netzwerk.
Werkzeuge der Go-Welt
| Werkzeug | Zweck |
|---|---|
go fmt / gofmt | einheitliche Formatierung (keine Diskussionen!) |
go vet | findet verdächtigen Code (falsche Formatverben, unerreichbarer Code ...) |
staticcheck, golangci-lint | weitere Analyse |
go doc, pkg.go.dev | Dokumentation |
gopls | Sprachserver für den Editor |
dlv (Delve) | Debugger |
pprof | Profiling (CPU, Speicher) |
go build -ldflags | Versionsinfo einbauen |
GOOS=linux GOARCH=amd64 go build | Cross-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, FunktionTestXyz(t *testing.T), Start mitgo test ./... - Tabellengetriebene Tests mit
t.Runsind der Standard -raceund-coverbei jedem Testlauf- Interfaces erleichtern Attrappen (Fakes)
go fmt,go vet,golangci-linthalten den Code sauber
Aufgabe
Schreibe eine Funktion FizzBuzz(n int) string mit einer Tabelle von Testfällen (1, 3, 5, 15, 7).