Go Testing 2026: Unit-Tests, Mocks und technische Interviewfragen

Ein umfassender Leitfaden zu Go-Testing-Praktiken im Jahr 2026, einschließlich Unit-Tests mit dem testing-Paket, Mocking mit gomock und häufigen Interviewfragen zu Go-Tests.

Go Testing 2026: Unit-Tests, Mocks und Interviewfragen

Go-Testing basiert auf dem Standard-testing-Paket, das mit jeder Go-Installation ausgeliefert wird und keine externen Abhängigkeiten erfordert. Im Gegensatz zu Frameworks in anderen Sprachen setzt Go auf Einfachheit: Testdateien befinden sich neben dem Produktionscode, Testfunktionen folgen einer Namenskonvention, und der go test-Befehl übernimmt die Erkennung und Ausführung.

Worauf Interviewer achten

Kandidaten, die tabellengesteuerte Tests schreiben, Interfaces für Dependency Injection verwenden und verstehen, wann Mocking einen Mehrwert bietet und wann es nur Rauschen erzeugt.

Unit-Tests mit dem testing-Paket schreiben

Jede Testdatei endet mit _test.go und befindet sich im selben Paket wie der zu testende Code. Testfunktionen beginnen mit Test gefolgt von einem großgeschriebenen Namen. Der *testing.T-Parameter stellt Methoden zur Meldung von Fehlern bereit.

calculator_test.gogo
package calculator

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

Die Ausführung von go test ./... führt alle Tests im aktuellen Modul aus. Das -v-Flag zeigt einzelne Testnamen und deren Bestanden/Nicht-Bestanden-Status an.

Tabellengesteuerte Tests: Der Go-Standard

Tabellengesteuerte Tests reduzieren Duplikation und machen das Hinzufügen neuer Testfälle trivial. Jede Zeile in der Tabelle repräsentiert ein Szenario mit Eingaben und erwarteten Ausgaben. Dieses Muster findet sich in der Go-Standardbibliothek und in Produktionscodebases der gesamten Branche.

validator_test.gogo
package validator

import "testing"

func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name    string
        email   string
        wantErr bool
    }{
        {"valid email", "user@example.com", false},
        {"missing at sign", "userexample.com", true},
        {"empty string", "", true},
        {"unicode local part", "user@例え.jp", false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := ValidateEmail(tt.email)
            if (err != nil) != tt.wantErr {
                t.Errorf("ValidateEmail(%q) error = %v, wantErr %v",
                    tt.email, err, tt.wantErr)
            }
        })
    }
}

Die t.Run-Methode erstellt Subtests, die unabhängig voneinander laufen. Fehlgeschlagene Subtests melden, welcher spezifische Fall fehlgeschlagen ist, ohne andere Fälle zu stoppen.

Dependency Injection durch Interfaces

Go-Interfaces ermöglichen das Testen von Code, der von externen Systemen abhängt: Datenbanken, HTTP-Clients, Dateisysteme. Die Definition eines minimalen Interfaces am Verwendungsort ermöglicht den Austausch realer Implementierungen gegen Test-Doubles.

service.gogo
package order

// Repository definiert die Datenzugriffsmethoden, die dieser Service benötigt
type Repository interface {
    FindByID(id string) (*Order, error)
    Save(order *Order) error
}

// Service behandelt die Order-Geschäftslogik
type Service struct {
    repo Repository
}

// NewService erstellt einen Service mit dem gegebenen Repository
func NewService(repo Repository) *Service {
    return &Service{repo: repo}
}

// Process validiert und speichert eine Bestellung
func (s *Service) Process(order *Order) error {
    if order.Total <= 0 {
        return ErrInvalidTotal
    }
    return s.repo.Save(order)
}

Der Service akzeptiert jeden Typ, der Repository erfüllt. Produktionscode übergibt eine datenbankgestützte Implementierung. Tests übergeben ein Mock oder Stub.

Mocking mit gomock und mockgen

Das gomock-Paket generiert Mock-Implementierungen aus Interface-Definitionen. Der mockgen-Befehl liest Quelldateien und erzeugt Mock-Structs, die Aufrufe aufzeichnen und konfigurierte Werte zurückgeben.

bash
# Mocks für das Repository-Interface generieren
mockgen -source=service.go -destination=mocks/repository_mock.go -package=mocks

Verwendung des generierten Mocks in Tests:

service_test.gogo
package order

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "go.uber.org/mock/gomock"

    "yourmodule/order/mocks"
)

func TestService_Process_SavesValidOrder(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockRepo := mocks.NewMockRepository(ctrl)

    order := &Order{ID: "123", Total: 99.99}

    // Erwarte, dass Save einmal mit dieser Bestellung aufgerufen wird, gib nil zurück
    mockRepo.EXPECT().
        Save(order).
        Return(nil).
        Times(1)

    svc := NewService(mockRepo)
    err := svc.Process(order)

    assert.NoError(t, err)
}

func TestService_Process_RejectsZeroTotal(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockRepo := mocks.NewMockRepository(ctrl)

    // Keine Aufrufe an repo erwartet, da die Validierung zuerst fehlschlägt

    svc := NewService(mockRepo)
    err := svc.Process(&Order{ID: "456", Total: 0})

    assert.ErrorIs(t, err, ErrInvalidTotal)
}

Das Mock verifiziert, dass Save genau einmal mit dem erwarteten Argument aufgerufen wurde. Wenn der zu testende Code Save nie aufruft oder mit anderen Argumenten aufruft, schlägt der Test fehl.

Verwendung von testify für Assertions und Suites

testify bietet Assertion-Funktionen, die klarere Fehlermeldungen erzeugen als manuelle if-Prüfungen. Das assert-Paket setzt die Ausführung nach Fehlern fort. Das require-Paket stoppt den Test sofort.

user_test.gogo
package user

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestParseUser(t *testing.T) {
    input := `{"id": "u1", "name": "Alice"}`

    user, err := ParseUser(input)

    require.NoError(t, err, "parsing should not fail")
    assert.Equal(t, "u1", user.ID)
    assert.Equal(t, "Alice", user.Name)
    assert.Empty(t, user.Email, "email should default to empty")
}

Der require.NoError-Aufruf stoppt den Test, wenn das Parsen fehlschlägt, und verhindert Nil-Pointer-Panics bei nachfolgenden Assertions.

Bereit für deine Go-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

HTTP-Handler mit httptest testen

Das net/http/httptest-Paket stellt ResponseRecorder zum Erfassen der Handler-Ausgabe und Server zum Ausführen eines lokalen Testservers bereit. Die meisten Unit-Tests verwenden ResponseRecorder, um Netzwerk-Overhead zu vermeiden.

handler_test.gogo
package api

import (
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestHealthHandler(t *testing.T) {
    req := httptest.NewRequest(http.MethodGet, "/health", nil)
    rec := httptest.NewRecorder()

    HealthHandler(rec, req)

    assert.Equal(t, http.StatusOK, rec.Code)
    assert.JSONEq(t, `{"status": "ok"}`, rec.Body.String())
}

Für Handler, die externe Dienste aufrufen, wird ein Mock-Client injiziert oder httptest.NewServer verwendet, um ein Fake-Backend zu erstellen.

Häufige Interviewfragen zu Go-Testing

Interviewer bewerten Testing-Wissen durch Fragen, die Erfahrung mit realen Codebasen offenbaren. Hier sind Muster, die vorbereitete Kandidaten auszeichnen:

"Wie würden Sie eine Funktion testen, die eine externe API aufruft?"

Definieren Sie ein Interface für den HTTP-Client, injizieren Sie es in die Funktion oder das Struct und stellen Sie ein Mock bereit, das vorbestimmte Antworten zurückgibt. Dies vermeidet Netzwerkaufrufe und macht Tests deterministisch.

"Wann würden Sie ein Stub versus ein Mock verwenden?"

Stubs geben vordefinierte Antworten zurück, ohne zu verifizieren, wie sie aufgerufen wurden. Mocks verifizieren, dass bestimmte Methoden mit bestimmten Argumenten aufgerufen wurden. Verwenden Sie Stubs, wenn der Rückgabewert wichtiger ist als die Interaktion. Verwenden Sie Mocks, wenn die Verifizierung der Interaktion der Zweck des Tests ist.

"Was sind tabellengesteuerte Tests und warum bevorzugt Go sie?"

Tabellengesteuerte Tests definieren Testfälle als Daten in einem Slice von Structs. Sie reduzieren Boilerplate, machen das Hinzufügen von Fällen trivial und erzeugen lesbare Testausgaben mit t.Run. Das Muster entspricht Gos Präferenz für expliziten, sich wiederholenden Code gegenüber cleveren Abstraktionen.

"Wie handhaben Sie Test-Fixtures oder Setup, das mehrere Tests teilen?"

Verwenden Sie TestMain für Setup und Teardown auf Paketebene. Verwenden Sie Hilfsfunktionen oder Test-Suites (von testify) für gemeinsames Setup über verwandte Tests hinweg. Vermeiden Sie globalen Zustand, der Kopplung zwischen Tests erzeugt.

Bereiten Sie sich auf ein Go-Interview vor? Das Go Testing-Modul behandelt diese Muster ausführlich mit Übungsfragen.

Tests mit Coverage und Race Detection ausführen

Der go test-Befehl akzeptiert Flags, die ungetestete Codepfade und Concurrency-Bugs aufdecken.

bash
# Tests mit Coverage-Bericht ausführen
go test -cover ./...

# HTML-Coverage-Visualisierung generieren
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html

# Race Conditions erkennen (langsamer, aber findet echte Bugs)
go test -race ./...

Der Race Detector instrumentiert Speicherzugriffe und meldet gleichzeitige Lese- und Schreibzugriffe auf dieselbe Variable. Die Ausführung von Tests mit -race in CI fängt Bugs ab, die sich nur unter bestimmtem Timing manifestieren.

Tests in großen Codebasen organisieren

Mit wachsenden Projekten beeinflusst die Testorganisation die Wartbarkeit:

  • Same-Package-Tests (package foo) haben Zugriff auf nicht-exportierte Funktionen und Felder. Für Unit-Tests verwenden, die internes Verhalten verifizieren.
  • External-Package-Tests (package foo_test) importieren das Paket wie externer Code. Für Integrationstests und zur Verifizierung der öffentlichen API verwenden.
  • Testdata-Verzeichnisse speichern Fixtures wie JSON-Dateien, Golden Outputs oder Seed-Datenbanken. Go ignoriert diese beim Build.
  • Build-Tags trennen Unit-Tests von Integrationstests, die externe Systeme erfordern. go test -tags=integration ./... ausführen, um sie einzubeziehen.

Für mehr Go-Best-Practices über Testing hinaus behandelt der Artikel über Concurrency-Muster Goroutines und Channels.

Was man über Go-Testing wissen sollte

  • Das testing-Paket wird mit Go ausgeliefert und erfordert keine externen Abhängigkeiten
  • Tabellengesteuerte Tests reduzieren Duplikation und sind das erwartete Muster in Interviews
  • Interfaces ermöglichen Dependency Injection; sie am Verwendungsort definieren, nicht bei der Implementierung
  • gomock generiert Mocks aus Interfaces; testify bietet sauberere Assertions
  • httptest.ResponseRecorder erfasst Handler-Ausgaben ohne Netzwerk-Overhead
  • Das -race-Flag erkennt Concurrency-Bugs, die Unit-Tests sonst übersehen
  • Coverage-Metriken lenken die Aufmerksamkeit, garantieren aber keine Korrektheit

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Tägliche Challenge

Findest du den Bug in Go?

Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Anthony Fillion-Maillet

Geschrieben von

Anthony Fillion-Maillet

Gründer von SharpSkill

Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.

Aktualisiert am 24. August 2026

Tags

#go
#testing
#unit-tests
#mocking
#interview

Teilen

Verwandte Artikel