Testen in Go: Unit-, Integrations- und E2E-Tests – ein vollständiger Leitfaden

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: Warum Tests in Go wichtig sind

Go (Golang) ist eine Sprache, die von ihrem Design her auf Einfachheit, Leistung und Zuverlässigkeit ausgelegt ist. Das integrierte testing-Paket und leistungsstarke Tools wie go test machen das Schreiben von Tests zu einem integralen Bestandteil der Entwicklung. Ohne qualitativ hochwertige Tests wird selbst der eleganteste Code irgendwann zu einem "fragilen" System, bei dem eine einzige Änderung alles zum Einsturz bringt.

In diesem Artikel analysieren wir drei Schlüsselebenen des Testens: Unit, Integration und E2E (End-to-End). Sie erfahren, wie sie sich unterscheiden, wann sie anzuwenden sind und wie man effektive Tests in Go schreibt. Das Material ist sowohl für Anfänger als auch für erfahrene Entwickler nützlich, die ihr Wissen systematisieren möchten.



1. Unit-Tests

Unit-Tests sind die Grundlage der Testpyramide. Sie überprüfen die Funktionsweise einzelner Funktionen, Methoden oder Module isoliert von externen Abhängigkeiten (Datenbanken, Netzwerk, Dateisystem).



Grundprinzipien von Unit-Tests in Go

  • Isolation: Der zu testende Code sollte nicht von externen Diensten abhängen.
  • Geschwindigkeit: Unit-Tests werden in Millisekunden ausgeführt.
  • Determinismus: Das Testergebnis sollte sich von einem Durchlauf zum nächsten nicht ändern.


Beispiel eines einfachen Unit-Tests

Angenommen, wir haben eine Funktion zum Addieren zweier Zahlen:

// math.gopackage math

func Add(a, b int) int { return a + b}

Der Test für diese Funktion würde wie folgt aussehen:

// math_test.gopackage math

import "testing"

func TestAdd(t *testing.T) { result := Add(2, 3) expected := 5

if result != expected { t.Errorf("Add(2, 3) = %d; want %d", result, expected) }}

Der Test wird mit dem Befehl ausgeführt: go test ./...



Tabellengetriebene Tests

Die Go-Community bevorzugt tabellengetriebene Tests zum Überprüfen mehrerer Fälle:

func TestAddTable(t *testing.T) {    tests := []struct {        a, b, expected int    }{        {1, 1, 2},        {0, 0, 0},        {-1, 1, 0},        {100, 200, 300},    }

for _, tt := range tests { result := Add(tt.a, tt.b) if result != tt.expected { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected) } }}

Tipp: Verwenden Sie t.Run(), um verschachtelte Tests mit aussagekräftigen Namen zu erstellen – das verbessert die Lesbarkeit der Berichte.



2. Integrationstests

Integrationstests überprüfen die Interaktion zwischen Systemkomponenten: Datenbank, API, externe Dienste. Im Gegensatz zu Unit-Tests werden hier reale (oder nahezu reale) Abhängigkeiten eingesetzt.



Besonderheiten von Integrationstests in Go

  • Erfordern oft eine Umgebungseinrichtung (z. B. eine Testdatenbank).
  • Können Build-Tags verwenden, um Unit- und Integrationstests zu trennen.
  • Laufen langsamer und werden daher separat ausgeführt.


Beispiel: Testen von Datenbankoperationen (PostgreSQL)

Erstellen wir eine Datei mit dem Build-Tag integration:

// db_test.go//go:build integration

package db

import ( "database/sql" "testing")

func TestSaveUser(t *testing.T) { // Verbindung zur Testdatenbank herstellen dsn := "host=localhost user=test password=test dbname=testdb sslmode=disable" db, err := sql.Open("postgres", dsn) if err != nil { t.Fatalf("failed to connect to db: %v", err) } defer db.Close()

// Daten vor dem Test bereinigen _, _ = db.Exec("TRUNCATE TABLE users")

// Die zu testende Operation ausführen user := User{Name: "Alice", Email: "alice@example.com"}

Blogs

Buchempfehlungen