Testing in Go: Unit, Integration, E2E — Vollständiger Leitfaden
Tests sind ein wesentlicher Bestandteil der Entwicklung qualitativ hochwertiger Software. In der Go-Sprache (Golang) ist das Testen in die Standardbibliothek integriert, was es einfach und effektiv macht. In diesem Artikel werden wir drei Hauptebenen des Testens untersuchen: Unit-, Integrations- und E2E-Tests. Sie erfahren, wie man sie schreibt, wann man sie anwendet und welche Werkzeuge man verwendet.
Warum ist Testen in Go so wichtig?
Go wurde mit Fokus auf Einfachheit und Zuverlässigkeit entwickelt. Das integrierte testing-Paket und das go test-Werkzeug erleichtern das Hinzufügen von Tests zu jedem Projekt. Das Go-Team folgt der Philosophie: "Wenn der Code nicht getestet ist, funktioniert er nicht." Testen hilft:
- Fehler früh im Entwicklungsprozess zu erkennen
- Code-Refactoring und -Wartung zu vereinfachen
- Das Verhalten von Funktionen und Modulen zu dokumentieren
- Das Vertrauen in die Anwendungsstabilität zu erhöhen
Unit-Tests in Go
Unit-Tests überprüfen die Funktionsweise einzelner Funktionen, Methoden oder Strukturen in Isolation von externen Abhängigkeiten. In Go werden sie in Dateien mit der Endung _test.go geschrieben.
Grundlagen von Unit-Tests
Betrachten wir ein einfaches Beispiel. Angenommen, wir haben eine Additionsfunktion:
// math.gopackage main
func Add(a, b int) int { return a + b}Der Test für diese Funktion würde wie folgt aussehen:
// math_test.gopackage main
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) }}Ausführen des Tests: go test -v
Tabellengetriebene Tests
Go fördert die Verwendung tabellengetriebener Tests zur Überprüfung mehrerer Szenarien:
func TestAddTableDriven(t *testing.T) { tests := []struct { name string a, b int want int }{ {"positive numbers", 2, 3, 5}, {"negative numbers", -1, -2, -3}, {"zero", 0, 0, 0}, {"mixed", -5, 10, 5}, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := Add(tt.a, tt.b) if got != tt.want { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want) } }) }}Testen mit Mocks
Um Unit-Tests zu isolieren, werden oft Mocks benötigt. In Go können Sie Interfaces und Bibliotheken wie gomock oder testify/mock verwenden.
// Define the interfacetype UserRepository interface { GetUser(id int) (User, error)}
// Test using testifyfunc TestGetUserName(t *testing.T) { mockRepo := new(MockUserRepository) mockRepo.On("GetUser", 1).Return(User{Name: "Alice"}, nil)
service := NewUserService(mockRepo) name, err := service.GetUserName(1)
assert.NoError(t, err) assert.Equal(t, "Alice", name) mockRepo.AssertExpectations(t)}Integrationstests in Go
Integrationstests überprüfen die Interaktion zwischen Systemkomponenten: Datenbanken, APIs, Dateisysteme und andere externe Dienste. In Go ist es üblich, sie in separaten Paketen zu platzieren oder Build-Tags zu verwenden.
Testen mit einer Datenbank
Für das Testen von Datenbankinteraktionen werden oft Testcontainer oder temporäre Datenbanken verwendet:
// integration_test.go (with build tag)//go:build integration
package integration
import ( "database/sql" "testing" _ "github.com/lib/pq")
func TestUserRepository(t *testing.T) { // Connection to test DB db, err := sql.Open("postgres", "host=localhost port=")