Tests Go en 2026 : Tests Unitaires, Mocks et Questions d'Entretien Technique

Guide complet sur les tests en Go : écriture de tests unitaires avec le package testing, création de mocks avec gomock, tests orientés tableaux et préparation aux entretiens techniques.

Tests unitaires et mocks en Go

Le framework de test de Go repose sur le package standard testing, inclus dans chaque installation de Go sans dépendance externe. Contrairement aux frameworks d'autres langages, l'approche de Go privilégie la simplicité : les fichiers de test cohabitent avec le code de production, les fonctions de test suivent une convention de nommage, et la commande go test gère la découverte et l'exécution.

Ce que recherchent les recruteurs

Les candidats qui écrivent des tests orientés tableaux, utilisent les interfaces pour l'injection de dépendances et comprennent quand le mocking apporte de la valeur plutôt que du bruit.

Écrire des Tests Unitaires avec le Package testing

Chaque fichier de test se termine par _test.go et appartient au même package que le code testé. Les fonctions de test commencent par Test suivi d'un nom en majuscule. Le paramètre *testing.T fournit des méthodes pour signaler les échecs.

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)
    }
}

L'exécution de go test ./... lance tous les tests du module courant. Le flag -v affiche les noms des tests individuels et leur statut.

Tests Orientés Tableaux : Le Standard Go

Les tests orientés tableaux réduisent la duplication et rendent l'ajout de nouveaux cas trivial. Chaque ligne du tableau représente un scénario avec ses entrées et sorties attendues. Ce pattern apparaît dans la bibliothèque standard Go et dans les bases de code en production.

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)
            }
        })
    }
}

La méthode t.Run crée des sous-tests qui s'exécutent indépendamment. Les sous-tests en échec indiquent quel cas spécifique a échoué sans arrêter les autres cas.

Injection de Dépendances par les Interfaces

Les interfaces Go permettent de tester du code qui dépend de systèmes externes : bases de données, clients HTTP, systèmes de fichiers. Définir une interface minimale au point d'utilisation permet de remplacer les implémentations réelles par des doubles de test.

service.gogo
package order

// Repository defines the data access methods this service needs
type Repository interface {
    FindByID(id string) (*Order, error)
    Save(order *Order) error
}

// Service handles order business logic
type Service struct {
    repo Repository
}

// NewService creates a service with the given repository
func NewService(repo Repository) *Service {
    return &Service{repo: repo}
}

// Process validates and saves an order
func (s *Service) Process(order *Order) error {
    if order.Total <= 0 {
        return ErrInvalidTotal
    }
    return s.repo.Save(order)
}

Le Service accepte tout type satisfaisant Repository. Le code de production passe une implémentation connectée à la base de données. Les tests passent un mock ou un stub.

Mocking avec gomock et mockgen

Le package gomock génère des implémentations mock à partir de définitions d'interface. La commande mockgen lit les fichiers source et produit des structs mock qui enregistrent les appels et retournent des valeurs configurées.

bash
# Generate mocks for the Repository interface
mockgen -source=service.go -destination=mocks/repository_mock.go -package=mocks

Utilisation du mock généré dans les 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}

    // Expect Save to be called once with this order, return nil
    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)

    // No calls expected to repo since validation fails first

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

    assert.ErrorIs(t, err, ErrInvalidTotal)
}

Le mock vérifie que Save a été appelé exactement une fois avec l'argument attendu. Si le code testé n'appelle jamais Save, ou l'appelle avec des arguments différents, le test échoue.

Utiliser testify pour les Assertions et les Suites

testify fournit des fonctions d'assertion qui produisent des messages d'échec plus clairs que les vérifications manuelles avec if. Le package assert continue l'exécution après les échecs. Le package require arrête le test immédiatement.

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")
}

L'appel require.NoError arrête le test si le parsing échoue, évitant les panics de pointeur nil sur les assertions suivantes.

Prêt à réussir tes entretiens Go ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Tester les Handlers HTTP avec httptest

Le package net/http/httptest fournit ResponseRecorder pour capturer la sortie des handlers et Server pour exécuter un serveur de test local. La plupart des tests unitaires utilisent ResponseRecorder pour éviter la surcharge réseau.

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())
}

Pour les handlers qui appellent des services externes, il convient d'injecter un client mock ou d'utiliser httptest.NewServer pour créer un backend fictif.

Questions d'Entretien Courantes sur les Tests Go

Les recruteurs évaluent les connaissances en matière de tests à travers des questions qui révèlent l'expérience avec des bases de code réelles. Voici les patterns qui distinguent les candidats préparés :

« Comment testeriez-vous une fonction qui appelle une API externe ? »

Il faut définir une interface pour le client HTTP, l'injecter dans la fonction ou la struct, et fournir un mock qui retourne des réponses prédéterminées. Cela évite les appels réseau et rend les tests déterministes.

« Quand utiliseriez-vous un stub plutôt qu'un mock ? »

Les stubs retournent des réponses préenregistrées sans vérifier comment ils ont été appelés. Les mocks vérifient que des méthodes spécifiques ont été appelées avec des arguments spécifiques. Les stubs sont préférables quand la valeur de retour importe plus que l'interaction. Les mocks conviennent quand la vérification de l'interaction est l'objectif du test.

« Que sont les tests orientés tableaux et pourquoi Go les privilégie-t-il ? »

Les tests orientés tableaux définissent les cas de test comme des données dans une slice de structs. Ils réduisent le code répétitif, rendent l'ajout de cas trivial et produisent une sortie de test lisible avec t.Run. Ce pattern s'aligne avec la préférence de Go pour un code explicite et répétitif plutôt que des abstractions complexes.

« Comment gérez-vous les fixtures de test ou le setup partagé par plusieurs tests ? »

Il est recommandé d'utiliser TestMain pour le setup et le teardown au niveau du package, et des fonctions helper ou des suites de tests (via testify) pour le setup partagé entre tests liés. L'état global qui crée un couplage entre les tests doit être évité.

Pour préparer un entretien Go, le module de tests Go couvre ces patterns en profondeur avec des questions pratiques.

Exécuter les Tests avec la Couverture et la Détection de Race Conditions

La commande go test accepte des flags qui révèlent les chemins de code non testés et les bugs de concurrence.

bash
# Run tests with coverage report
go test -cover ./...

# Generate HTML coverage visualization
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html

# Detect race conditions (slower, but catches real bugs)
go test -race ./...

Le détecteur de race instrumente les accès mémoire et signale les lectures et écritures concurrentes sur la même variable. Exécuter les tests avec -race en CI permet de détecter des bugs qui ne se manifestent que dans certaines conditions de timing.

Organiser les Tests dans les Grandes Bases de Code

À mesure que les projets grandissent, l'organisation des tests affecte la maintenabilité :

  • Tests du même package (package foo) accèdent aux fonctions et champs non exportés. À utiliser pour les tests unitaires qui vérifient le comportement interne.
  • Tests de package externe (package foo_test) importent le package comme du code externe. À utiliser pour les tests d'intégration et pour vérifier l'API publique.
  • Répertoires testdata stockent les fixtures comme les fichiers JSON, les sorties de référence ou les bases de données de seed. Go les ignore lors des builds.
  • Build tags séparent les tests unitaires des tests d'intégration qui nécessitent des systèmes externes. Exécuter go test -tags=integration ./... pour les inclure.

Pour en savoir plus sur les bonnes pratiques Go au-delà des tests, l'article sur les patterns de concurrence couvre les goroutines et les channels.

Points Essentiels à Retenir sur les Tests Go

  • Le package testing est inclus avec Go et ne nécessite aucune dépendance externe
  • Les tests orientés tableaux réduisent la duplication et constituent le pattern attendu en entretien
  • Les interfaces permettent l'injection de dépendances ; elles doivent être définies au point d'utilisation, pas à l'implémentation
  • gomock génère des mocks à partir des interfaces ; testify fournit des assertions plus claires
  • httptest.ResponseRecorder capture la sortie des handlers sans surcharge réseau
  • Le flag -race détecte les bugs de concurrence que les tests unitaires manquent autrement
  • Les métriques de couverture guident l'attention mais ne garantissent pas la correction

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Défi du jour

Tu saurais repérer le bug en Go ?

Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Anthony Fillion-Maillet

Écrit par

Anthony Fillion-Maillet

Fondateur de SharpSkill

Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.

Mis à jour le 24 août 2026

Tags

#go
#testing
#unit-tests
#mocks
#gomock
#testify

Partager

Articles similaires