Einleitung: Warum Testen in Go eine Notwendigkeit ist
\nGo (Golang) ist eine moderne Programmiersprache, die für den Bau zuverlässiger und skalierbarer Systeme entwickelt wurde. Doch selbst der eleganteste Code ist nicht vor Fehlern gefeit. Deshalb ist das Testen ein wesentlicher Bestandteil der Entwicklung in Go. Das integrierte Paket testing bietet leistungsstarke Werkzeuge zum Schreiben von Unit-Tests, Integrationstests und sogar End-to-End (E2E)-Prüfungen.
In diesem Artikel werden wir alle Testebenen in Go durchgehen: von einfachen Unit-Tests bis hin zu komplexen E2E-Szenarien. Sie erfahren, wie Sie Tests richtig strukturieren, Mocks verwenden, table-driven Tests schreiben und Integrationstests mit echten Abhängigkeiten einrichten. Das Material ist sowohl für Anfänger als auch für erfahrene Entwickler nützlich, die ihr Wissen systematisieren möchten.
\n\n1. Unit-Testing in Go: Die Grundlage
\nUnit-Tests überprüfen die Arbeit einzelner Funktionen oder Methoden isoliert von externen Abhängigkeiten. In Go wird dafür das Standardpaket testing verwendet. Grundregeln: Testdateien müssen *_test.go heißen, und Testfunktionen müssen mit Test beginnen.
1.1. Ein einfacher Unit-Test
\nBetrachten wir eine Funktion zur Addition zweier Zahlen:
\n// math.go\npackage math\n\nfunc Add(a, b int) int {\n return a + b\n}\nDer Test dafür:
\n// math_test.go\npackage math\n\nimport "testing"\n\nfunc TestAdd(t *testing.T) {\n result := Add(2, 3)\n expected := 5\n if result != expected {\n t.Errorf("Add(2, 3) = %d; want %d", result, expected)\n }\n}\nTests ausführen: go test ./... oder go test -v für detaillierte Ausgabe.
1.2. Table-Driven Tests (Tabellengesteuerte Tests)
\nDie Go-Community verwendet häufig den table-driven Ansatz, um mehrere Fälle zu prüfen:
\nfunc TestAddTableDriven(t *testing.T) {\n tests := []struct {\n name string\n a, b int\n want int\n }{\n {"positive", 2, 3, 5},\n {"negative", -1, -2, -3},\n {"zero", 0, 0, 0},\n }\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n if got := Add(tt.a, tt.b); got != tt.want {\n t.Errorf("Add() = %d, want %d", got, tt.want)\n }\n })\n }\n}\nDieser Ansatz macht Tests leicht lesbar und erweiterbar.
\n\n1.3. Testen mit Fehlern
\nFür Funktionen, die Fehler zurückgeben, verwenden Sie t.Fatal oder t.Errorf:
func TestDivide(t *testing.T) {\n _, err := Divide(10, 0)\n if err == nil {\n t.Fatal("expected error for division by zero")\n }\n}\n\n2. Integrationstests: Arbeiten mit Abhängigkeiten
\nIntegrationstests überprüfen die Interaktion von Komponenten: Datenbanken, APIs, Dateisysteme. In Go werden sie üblicherweise mit Build-Tags oder einer separaten Paketstruktur von Unit-Tests getrennt.
\n\n2.1. Verwendung von Build-Tags
\nErstellen Sie eine Datei mit dem Tag //go:build integration am Anfang:
//go:build integration\n\npackage db\n\nimport (\n "testing"\n "database/sql"\n _ "github.com/lib/pq"\n)\n\nfunc TestPostgresConnection(t *testing.T) {\n dsn := "host=localhost port=5432 user=test password=test dbname=test sslmode=disable"\n db, err := sql.Open("postgres", dsn)\n if err != nil {\n t.Fatalf("failed to open db: %v", err)\n }\n defer db.Close()\n \n if err := db.Ping(); err != nil {\n t.Fatalf("failed to ping db: %v", err)\n }\n}\nNur Integrationstests ausführen: go test -tags=integration ./...
2.2. Mocks und Stubs
\nVerwenden Sie Interfaces und Mocks zur Isolation von Tests. Beispiel mit der beliebten Bibliothek gomock:
// user.go\ntype UserRepository interface {\n GetUser(id int) (*Us