Tests en Go : Unit, Integration, E2E — guide complet
Les tests sont une partie essentielle du développement de logiciels de qualité. Dans le langage Go (Golang), les tests sont intégrés au niveau de la bibliothèque standard, ce qui les rend simples et efficaces. Dans cet article, nous allons aborder les trois principaux niveaux de tests : les tests unitaires (unit), les tests d'intégration (integration) et les tests de bout en bout (e2e). Vous apprendrez à les écrire, quand les utiliser et quels outils employer.
Pourquoi les tests en Go sont-ils si importants ?
Go a été créé avec un accent sur la simplicité et la fiabilité. Le package intégré testing et l'outil go test permettent d'ajouter facilement des tests à n'importe quel projet. L'équipe Go adhère à la philosophie suivante : "Si le code n'est pas testé, il ne fonctionne pas". Les tests aident à :
- Détecter les erreurs dès les premières étapes du développement
- Simplifier le refactoring et la maintenance du code
- Documenter le comportement des fonctions et des modules
- Renforcer la confiance dans la stabilité de l'application
Tests unitaires en Go
Les tests unitaires vérifient le fonctionnement de fonctions, méthodes ou structures individuelles, isolées des dépendances externes. En Go, ils sont écrits dans des fichiers avec le suffixe _test.go.
Bases des tests unitaires
Prenons un exemple simple. Supposons que nous ayons une fonction d'addition :
// math.gopackage main
func Add(a, b int) int { return a + b}Le test pour cette fonction ressemblera à ceci :
// 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) }}Exécution du test : go test -v
Tests pilotés par tableaux (Table-driven tests)
Go encourage l'utilisation de tests pilotés par tableaux pour vérifier plusieurs scénarios :
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) } }) }}Tests avec des mocks
Pour isoler les tests unitaires, des mocks sont souvent nécessaires. En Go, on peut utiliser des interfaces et des bibliothèques telles que gomock ou testify/mock.
// Définition de l'interfacetype UserRepository interface { GetUser(id int) (User, error)}
// Test avec 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)}Tests d'intégration en Go
Les tests d'intégration vérifient l'interaction entre les composants du système : bases de données, API, systèmes de fichiers et autres services externes. En Go, il est courant de les placer dans des packages séparés ou d'utiliser des build tags.
Tests avec une base de données
Pour tester le travail avec une base de données, on utilise souvent testcontainers ou des bases de données temporaires :
// integration_test.go (avec build tag)//go:build integration
package integration
import ( "database/sql" "testing" _ "github.com/lib/pq")
func TestUserRepository(t *testing.T) { // Connexion à la base de données de test db, err := sql.Open("postgres", "host=localhost port=")