Communitygithub.com

modèles-de-test-go

Modèles de test Go incluant tests pilotés par tableaux, sous-tests, benchmarks, fuzzing et couverture de code. Suit la méthodologie TDD avec des pratiques Go idiomatiques.

Qu'est-ce que modèles-de-test-go ?

Cette compétence fournit un guide complet sur les modèles de test en Go, en suivant la méthodologie TDD (Test-Driven Development) avec des pratiques idiomatiques. Elle couvre le cycle RED-GREEN-REFACTOR, les tests pilotés par tableaux, les sous-tests, les helpers de test, les fichiers golden, le mocking basé sur les interfaces, les benchmarks, le fuzzing (dès Go 1.18), et la couverture de test. Chaque section inclut des exemples de code détaillés et des bonnes pratiques. La compétence est conçue pour les développeurs Go qui souhaitent écrire des tests fiables et maintenables, que ce soit pour de nouvelles fonctions, l'ajout de couverture à du code existant, ou la création de benchmarks pour du code sensible aux performances. Elle supporte tous les environnements de développement Go standard (comme les IDE, les éditeurs de texte, et les pipelines CI/CD) et s'intègre avec les outils de test natifs de Go. Son utilisation aide à structurer les tests de manière claire et efficace, en suivant les conventions de la communauté Go.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/golang-testing

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Modèles de Test Go

Modèles de test Go complets pour écrire des tests fiables et maintenables selon la méthodologie TDD.

Quand Activer

  • Écrire de nouvelles fonctions ou méthodes Go
  • Ajouter une couverture de test à du code existant
  • Créer des benchmarks pour du code sensible aux performances
  • Implémenter des tests de fuzzing pour la validation d'entrée
  • Suivre un workflow TDD dans des projets Go

Workflow TDD pour Go

Le Cycle RED-GREEN-REFACTOR

RED     → Écrire d'abord un test qui échoue
GREEN   → Écrire le code minimal pour passer le test
REFACTOR → Améliorer le code tout en gardant les tests verts
REPEAT  → Continuer avec l'exigence suivante

TDD Pas à Pas en Go

// Étape 1 : Définir l'interface/la signature
// calculator.go
package calculator

func Add(a, b int) int {
    panic("not implemented") // Espace réservé
}

// Étape 2 : Écrire un test qui échoue (RED)
// calculator_test.go
package calculator

import "testing"

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

// Étape 3 : Exécuter le test - vérifier l'ÉCHEC
// $ go test
// --- FAIL: TestAdd (0.00s)
// panic: not implemented

// Étape 4 : Implémenter le code minimal (GREEN)
func Add(a, b int) int {
    return a + b
}

// Étape 5 : Exécuter le test - vérifier le SUCCÈS
// $ go test
// PASS

// Étape 6 : Refactoriser si nécessaire, vérifier que les tests passent toujours

Tests Pilotés par Tableaux

Le modèle standard pour les tests Go. Permet une couverture complète avec un code minimal.

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -1, -2, -3},
        {"zero values", 0, 0, 0},
        {"mixed signs", -1, 1, 0},
        {"large numbers", 1000000, 2000000, 3000000},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Add(tt.a, tt.b)
            if got != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d",
                    tt.a, tt.b, got, tt.expected)
            }
        })
    }
}

Tests Pilotés par Tableaux avec Cas d'Erreur

func TestParseConfig(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    *Config
        wantErr bool
    }{
        {
            name:  "valid config",
            input: `{"host": "localhost", "port": 8080}`,
            want:  &Config{Host: "localhost", Port: 8080},
        },
        {
            name:    "invalid JSON",
            input:   `{invalid}`,
            wantErr: true,
        },
        {
            name:    "empty input",
            input:   "",
            wantErr: true,
        },
        {
            name:  "minimal config",
            input: `{}`,
            want:  &Config{}, // Configuration de valeur nulle
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := ParseConfig(tt.input)

            if tt.wantErr {
                if err == nil {
                    t.Error("expected error, got nil")
                }
                return
            }

            if err != nil {
                t.Fatalf("unexpected error: %v", err)
            }

            if !reflect.DeepEqual(got, tt.want) {
                t.Errorf("got %+v; want %+v", got, tt.want)
            }
        })
    }
}

Sous-tests et Sous-benchmarks

Organiser des Tests Connexes

func TestUser(t *testing.T) {
    // Configuration partagée par tous les sous-tests
    db := setupTestDB(t)

    t.Run("Create", func(t *testing.T) {
        user := &User{Name: "Alice"}
        err := db.CreateUser(user)
        if err != nil {
            t.Fatalf("CreateUser failed: %v", err)
        }
        if user.ID == "" {
            t.Error("expected user ID to be set")
        }
    })

    t.Run("Get", func(t *testing.T) {
        user, err := db.GetUser("alice-id")
        if err != nil {
            t.Fatalf("GetUser failed: %v", err)
        }
        if user.Name != "Alice" {
            t.Errorf("got name %q; want %q", user.Name, "Alice")
        }
    })

    t.Run("Update", func(t *testing.T) {
        // ...
    })

    t.Run("Delete", func(t *testing.T) {
        // ...
    })
}

Sous-tests Parallèles

func TestParallel(t *testing.T) {
    tests := []struct {
        name  string
        input string
    }{
        {"case1", "input1"},
        {"case2", "input2"},
        {"case3", "input3"},
    }

    for _, tt := range tests {
        tt := tt // Capturer la variable de plage
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel() // Exécuter les sous-tests en parallèle
            result := Process(tt.input)
            // assertions...
            _ = result
        })
    }
}

Helpers de Test

Fonctions Helper

func setupTestDB(t *testing.T) *sql.DB {
    t.Helper() // Marque ceci comme une fonction helper

    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        t.Fatalf("failed to open database: %v", err)
    }

    // Nettoyage à la fin du test
    t.Cleanup(func() {
        db.Close()
    })

    // Exécuter les migrations
    if _, err := db.Exec(schema); err != nil {
        t.Fatalf("failed to create schema: %v", err)
    }

    return db
}

func assertNoError(t *testing.T, err error) {
    t.Helper()
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
}

func assertEqual[T comparable](t *testing.T, got, want T) {
    t.Helper()
    if got != want {
        t.Errorf("got %v; want %v", got, want)
    }
}

Fichiers et Répertoires Temporaires

func TestFileProcessing(t *testing.T) {
    // Créer un répertoire temporaire - automatiquement nettoyé
    tmpDir := t.TempDir()

    // Créer un fichier de test
    testFile := filepath.Join(tmpDir, "test.txt")
    err := os.WriteFile(testFile, []byte("test content"), 0644)
    if err != nil {
        t.Fatalf("failed to create test file: %v", err)
    }

    // Exécuter le test
    result, err := ProcessFile(testFile)
    if err != nil {
        t.Fatalf("ProcessFile failed: %v", err)
    }

    // Assert...
    _ = result
}

Fichiers Golden (Fichiers de Référence)

Tester par rapport à des fichiers de sortie attendus stockés dans testdata/.

var update = flag.Bool("update", false, "update golden files")

func TestRender(t *testing.T) {
    tests := []struct {
        name  string
        input Template
    }{
        {"simple", Template{Name: "test"}},
        {"complex", Template{Name: "test", Items: []string{"a", "b"}}},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Render(tt.input)

            golden := filepath.Join("testdata", tt.name+".golden")

            if *update {
                // Mettre à jour le fichier golden : go test -update
                err := os.WriteFile(golden, got, 0644)
                if err != nil {
                    t.Fatalf("failed to update golden file: %v", err)
                }
            }

            want, err := os.ReadFile(golden)
            if err != nil {
                t.Fatalf("failed to read golden file: %v", err)
            }

            if !bytes.Equal(got, want) {
                t.Errorf("output mismatch:\ngot:\n%s\nwant:\n%s", got, want)
            }
        })
    }
}

Mocking avec Interfaces

Mocking Basé sur les Interfaces

// Définir une interface pour les dépendances
type UserRepository interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

// Implémentation de production
type PostgresUserRepository struct {
    db *sql.DB
}

func (r *PostgresUserRepository) GetUser(id string) (*User, error) {
    // Requête à la base de données réelle
}

// Implémentation mock pour les tests
type MockUserRepository struct {
    GetUserFunc  func(id string) (*User, error)
    SaveUserFunc func(user *User) error
}

func (m *MockUserRepository) GetUser(id string) (*User, error) {
    return m.GetUserFunc(id)
}

func (m *MockUserRepository) SaveUser(user *User) error {
    return m.SaveUserFunc(user)
}

// Test utilisant le mock
func TestUserService(t *testing.T) {
    mock := &MockUserRepository{
        GetUserFunc: func(id string) (*User, error) {
            if id == "123" {
                return &User{ID: "123", Name: "Alice"}, nil
            }
            return nil, ErrNotFound
        },
    }

    service := NewUserService(mock)

    user, err := service.GetUserProfile("123")
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if user.Name != "Alice" {
        t.Errorf("got name %q; want %q", user.Name, "Alice")
    }
}

Benchmarks

Benchmarks de Base

func BenchmarkProcess(b *testing.B) {
    data := generateTestData(1000)
    b.ResetTimer() // Ne pas compter le temps de configuration

    for i := 0; i < b.N; i++ {
        Process(data)
    }
}

// Exécution : go test -bench=BenchmarkProcess -benchmem
// Sortie : BenchmarkProcess-8   10000   105234 ns/op   4096 B/op   10 allocs/op

Benchmark avec Différentes Tailles

func BenchmarkSort(b *testing.B) {
    sizes := []int{100, 1000, 10000, 100000}

    for _, size := range sizes {
        b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
            data := generateRandomSlice(size)
            b.ResetTimer()

            for i := 0; i < b.N; i++ {
                // Faire une copie pour éviter de trier des données déjà triées
                tmp := make([]int, len(data))
                copy(tmp, data)
                sort.Ints(tmp)
            }
        })
    }
}

Benchmarks d'Allocation Mémoire

func BenchmarkStringConcat(b *testing.B) {
    parts := []string{"hello", "world", "foo", "bar", "baz"}

    b.Run("plus", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            var s string
            for _, p := range parts {
                s += p
            }
            _ = s
        }
    })

    b.Run("builder", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            var sb strings.Builder
            for _, p := range parts {
                sb.WriteString(p)
            }
            _ = sb.String()
        }
    })

    b.Run("join", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            _ = strings.Join(parts, "")
        }
    })
}

Fuzzing (Go 1.18+)

Test de Fuzzing de Base

func FuzzParseJSON(f *testing.F) {
    // Ajouter un corpus de départ
    f.Add(`{"name": "test"}`)
    f.Add(`{"count": 123}`)
    f.Add(`[]`)
    f.Add(`""`)

    f.Fuzz(func(t *testing.T, input string) {
        var result map[string]interface{}
        err := json.Unmarshal([]byte(input), &result)

        if err != nil {
            // Un JSON invalide est attendu pour une entrée aléatoire
            return
        }

        // Si l'analyse a réussi, un ré-encodage devrait fonctionner
        _, err = json.Marshal(result)
        if err != nil {
            t.Errorf("Marshal failed after successful Unmarshal: %v", err)
        }
    })
}

// Exécution : go test -fuzz=FuzzParseJSON -fuzztime=30s

Test de Fuzzing avec Entrées Multiples

func FuzzCompare(f *testing.F) {
    f.Add("hello", "world")
    f.Add("", "")
    f.Add("abc", "abc")

    f.Fuzz(func(t *testing.T, a, b string) {
        result := Compare(a, b)

        // Propriété : Compare(a, a) devrait toujours être égal à 0
        if a == b && result != 0 {
            t.Errorf("Compare(%q, %q) = %d; want 0", a, b, result)
        }

        // Propriété : Compare(a, b) et Compare(b, a) devraient avoir des signes opposés
        reverse := Compare(b, a)
        if (result > 0 && reverse >= 0) || (result < 0 && reverse <= 0) {
            if result != 0 || reverse != 0 {
                t.Errorf("Compare(%q, %q) = %d, Compare(%q, %q) = %d; inconsistent",
                    a, b, result, b, a, reverse)
            }
        }
    })
}

Couverture de Test

Exécuter la Couverture

# Couverture de base
go test -cover ./...

# Générer un profil de couverture
go test -coverprofile=coverage.out ./...

# Voir la couverture dans le navigateur
go tool cover -html=coverage.out

# Voir la couverture par fonction
go tool cover -func=coverage.out

# Couverture avec détection de course
go test -race -coverprofile=coverage.out ./...

Cibles de Couverture

Type de CodeCible
Logique métier critique100%
APIs publiques90%+
Code général80%+
Code généréExclure

Exclure le Code Généré de la Couverture

//go:generate mockgen -source=interface.go -destination=mock_interface.go

// Dans le profil de couverture, exclure avec des tags de build :
// go test -cover -tags=!generate ./...

Test de Gestionnaires HTTP

func TestHealthHandler(t *testing.T) {
    // Créer une requête
    req := httptest.NewRequest(http.MethodGet, "/health", nil)
    w := httptest.NewRecorder()

    // Appeler le gestionnaire
    HealthHandler(w, req)

    // Vérifier la réponse
    resp := w.Result()
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        t.Errorf("got status %d; want %d", resp.StatusCode, http.StatusOK)
    }

    body, _ := io.ReadAll(resp.Body)
    if string(body) != "OK" {
        t.Errorf("got body %q; want %q", body, "OK")
    }
}

func TestAPIHandler(t *testing.T) {
    tests := []struct {
        name       string
        method     string
        path       string
        body       string
        wantStatus int
        wantBody   string
    }{
        {
            name:       "get user",
            method:     http.MethodGet,
            path:       "/users/123",
            wantStatus: http.StatusOK,
            wantBody:   `{"id":"123","name":"Alice"}`,
        },
        {
            name:       "not found",
            method:     http.MethodGet,
            path:       "/users/999",
            wantStatus: http.StatusNotFound,
        },
        {
            name:       "create user",
            method:     http.MethodPost,
            path:       "/users",
            body:       `{"name":"Bob"}`,
            wantStatus: http.StatusCreated,
        },
    }

    handler := NewAPIHandler()

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            var bodyReader io.Reader
            if tt.body != "" {
                bodyReader = strings.NewReader(tt.body)
            }

            req := httptest.NewRequest(tt.method, tt.path, bodyReader)
            w := httptest.NewRecorder()

            handler.ServeHTTP(w, req)

            resp := w.Result()
            defer resp.Body.Close()

            if resp.StatusCode != tt.wantStatus {
                t.Errorf("got status %d; want %d", resp.StatusCode, tt.wantStatus)
            }

            if tt.wantBody != "" {
                body, _ := io.ReadAll(resp.Body)
                if string(body) != tt.wantBody {
                    t.Errorf("got body %q; want %q", body, tt.wantBody)
                }
            }
        })
    }
}

Bonnes Pratiques

Nommage des Tests

  • Utiliser des noms de test descriptifs : TestFunctionName_Scenario_ExpectedResult
  • Nommer les sous-tests de manière claire : t.Run("invalid input returns error", ...)
  • Éviter les noms de test vagues comme TestIt ou TestStuff

Organisation des Tests

  • Placer les tests dans le même package (package foo) pour les tests de boîte blanche
  • Utiliser package foo_test pour les tests de boîte noire (tests d'intégration)
  • Suivre la convention : foo.go → foo_test.go
  • Maintenir les fichiers de test près du code source

Indépendance des Tests

  • Écrire des tests atomiques qui peuvent s'exécuter dans n'importe quel ordre
  • Éviter l'état partagé entre les tests
  • Utiliser des helpers t.Cleanup pour un nettoyage fiable
  • Réinitialiser les mocks entre les tests

Messages d'Erreur

  • Utiliser des messages d'erreur formatés : t.Errorf("DoSomething(%v) = %v; want %v", input, got, want)
  • Inclure à la fois les valeurs réelles et attendues
  • Utiliser t.Fatalf pour les erreurs fatales qui empêchent la poursuite des tests
  • Éviter les messages génériques : t.Error("it failed")

Commandes Couramment Utilisées

# Exécuter tous les tests
go test ./...

# Exécuter avec sortie détaillée
go test -v ./...

# Exécuter un seul test
go test -run TestFunctionName ./...

# Exécuter un sous-test spécifique
go test -run "TestName/SubtestName" ./...

# Exécuter des tests en parallèle
go test -parallel 4 ./...

# Exécuter des benchmarks
go test -bench=. ./...

# Exécuter des tests de fuzzing
go test -fuzz=. -fuzztime=30s

# Voir la couverture
go test -cover ./...

# Générer un rapport de couverture
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

# Exécuter avec détection de course
go test -race ./...

# Voir les tests ignorés
go test -v ./... 2>&1 | grep -E "(PASS|FAIL|SKIP)"

Références


Maintenu par l'équipe Go Patterns. Adapté à tous les projets Go suivant TDD.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

Skills associés