Integrationstests mit Go und Docker: Fortgeschrittenes Niveau
Integrationstests sind ein kritisch wichtiger Schritt bei der Entwicklung zuverlässiger Mikroservices in Go. Isolierte Unit-Tests garantieren nicht, dass Ihre Anwendung korrekt mit externen Abhängigkeiten interagiert: Datenbanken, Nachrichtenwarteschlangen, Caches und Drittanbieter-APIs. Docker ermöglicht es, reale Instanzen dieser Dienste in der Testumgebung hochzufahren und so maximale Zuverlässigkeit der Prüfungen zu gewährleisten. In diesem Artikel analysieren wir fortgeschrittene Techniken für Integrationstests von Go-Anwendungen mit Docker.
Warum Docker für Integrationstests in Go wichtig ist?
Der traditionelle Ansatz mit Mocks und Stubs prüft nur die Logik Ihres Codes, nicht jedoch das Verhalten im Zusammenspiel mit einer echten Datenbank oder einem Broker. Docker löst dieses Problem:
- Reproduzierbarkeit der Umgebung: Tests laufen unter identischen Bedingungen lokal und in CI/CD.
- Isolation: Jeder Testsatz kann seine eigene Dienstinstanz haben.
- Keine "Magie": Sie testen genau den Treiber/Client, den Sie auch in der Produktion verwenden.
Die manuelle Verwaltung von Containern über os/exec oder Shell-Skripte wird jedoch schnell instabil. Hier helfen Bibliotheken wie testcontainers-go.
Die Testcontainers-Bibliothek für Go: Installation und Basisbeispiel
testcontainers-go ist ein Go-Wrapper, der den Lebenszyklus von Docker-Containern programmatisch verwaltet. Er lädt automatisch Images herunter, startet sie, wartet auf ihre Bereitschaft und räumt Container nach dem Test auf.
Installation
go get github.com/testcontainers/testcontainers-gogo get github.com/testcontainers/testcontainers-go/modules/postgresBasisbeispiel: Testen mit PostgreSQL
package integration
import ( "context" "database/sql" "testing" "time"
"github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/postgres" "github.com/testcontainers/testcontainers-go/wait" _ "github.com/lib/pq")
func TestPostgresConnection(t *testing.T) { ctx := context.Background()
// 1. PostgreSQL-Container starten pgContainer, err := postgres.RunContainer(ctx, testcontainers.WithImage("postgres:15"), postgres.WithDatabase("testdb"), postgres.WithUsername("user"), postgres.WithPassword("password"), testcontainers.WithWaitStrategy( wait.ForLog("database system is ready to accept connections"). WithOccurrence(2). WithStartupTimeout(30*time.Second)), ) if err != nil { t.Fatal(err) } defer pgContainer.Terminate(ctx) // Automatische Bereinigung
// 2. Verbindungsstring abrufen connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable") if err != nil { t.Fatal(err) }
// 3. Verbinden und Abfrage ausführen db, err := sql.Open("postgres", connStr) if err != nil { t.Fatal(err) } defer db.Close()
if err := db.Ping(); err != nil { t.Fatal("Verbindung zur Datenbank fehlgeschlagen:", err) }
// Prüfen, ob wir eine Tabelle erstellen und Daten einfügen können _, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT)`) if err != nil { t.Fatal(err) } t.Log("Integrationstest mit PostgreSQL erfolgreich bestanden!")}Fortgeschrittene Techniken: Parallele Ausführung und benutzerdefinierte Wartezeiten
Wenn es viele Tests gibt, verlangsamt das sequenzielle Starten von Containern für jeden Test den Durchlauf. Wir nutzen Go-Parallelität und Shared-Container.
Parallele Tests mit gemeinsam genutztem Container
Erstellen eines Containers für das gesamte Paket über TestMain:
package integrat