Introduction : Pourquoi les tests en Go sont-ils une nécessité ?
\nGo (Golang) est un langage de programmation moderne conçu pour construire des systèmes fiables et évolutifs. Cependant, même le code le plus élégant n'est pas à l'abri des erreurs. C'est pourquoi les tests sont une partie intégrante du développement en Go. Le package intégré testing fournit une boîte à outils puissante pour écrire des tests unitaires, des tests d'intégration et même des vérifications de bout en bout (e2e).
Dans cet article, nous aborderons tous les niveaux de tests en Go : des simples tests unitaires aux scénarios e2e complexes. Vous apprendrez à structurer correctement les tests, à utiliser des mocks, à écrire des tests table-driven et à configurer des tests d'intégration avec des dépendances réelles. Ce contenu sera utile aussi bien aux débutants qu'aux développeurs expérimentés souhaitant systématiser leurs connaissances.
\n\n1. Tests unitaires en Go : les bases fondamentales
\nLes tests unitaires vérifient le fonctionnement de fonctions ou méthodes individuelles isolées des dépendances externes. En Go, on utilise le package standard testing. Les règles principales : les fichiers de test doivent être nommés *_test.go et les fonctions de test doivent commencer par Test.
1.1. Test unitaire simple
\nConsidérons une fonction d'addition de deux nombres :
\n// math.go\npackage math\n\nfunc Add(a, b int) int {\n return a + b\n}\nTest pour celle-ci :
\n// math_test.go\npackage math\n\nimport "testing"\n\nfunc TestAdd(t *testing.T) {\n result := Add(2, 3)\n expected := 5\n if result != expected {\n t.Errorf("Add(2, 3) = %d; want %d", result, expected)\n }\n}\nExécution des tests : go test ./... ou go test -v pour une sortie détaillée.
1.2. Tests table-driven (tests tabulaires)
\nLa communauté Go utilise activement l'approche table-driven pour vérifier de nombreux cas :
\nfunc TestAddTableDriven(t *testing.T) {\n tests := []struct {\n name string\n a, b int\n want int\n }{\n {"positive", 2, 3, 5},\n {"negative", -1, -2, -3},\n {"zero", 0, 0, 0},\n }\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n if got := Add(tt.a, tt.b); got != tt.want {\n t.Errorf("Add() = %d, want %d", got, tt.want)\n }\n })\n }\n}\nCette approche rend les tests faciles à lire et à étendre.
\n\n1.3. Tests avec erreurs
\nPour les fonctions qui retournent des erreurs, utilisez t.Fatal ou t.Errorf :
func TestDivide(t *testing.T) {\n _, err := Divide(10, 0)\n if err == nil {\n t.Fatal("expected error for division by zero")\n }\n}\n\n2. Tests d'intégration : travailler avec les dépendances
\nLes tests d'intégration vérifient l'interaction des composants : base de données, API, système de fichiers. En Go, on les sépare généralement des tests unitaires à l'aide de build tags ou d'une structure de packages distincte.
\n\n2.1. Utilisation des build tags
\nCréez un fichier avec le tag //go:build integration au début :
//go:build integration\n\npackage db\n\nimport (\n "testing"\n "database/sql"\n _ "github.com/lib/pq"\n)\n\nfunc TestPostgresConnection(t *testing.T) {\n dsn := "host=localhost port=5432 user=test password=test dbname=test sslmode=disable"\n db, err := sql.Open("postgres", dsn)\n if err != nil {\n t.Fatalf("failed to open db: %v", err)\n }\n defer db.Close()\n \n if err := db.Ping(); err != nil {\n t.Fatalf("failed to ping db: %v", err)\n }\n}\nExécution uniquement des tests d'intégration : go test -tags=integration ./...
2.2. Mocks et stubs
\nPour isoler les tests, utilisez des interfaces et des mocks. Exemple avec la bibliothèque populaire gomock :
// user.go\ntype UserRepository interface {\n GetUser(id int) (*Us