Tests d'intégration Go avec Docker : de la théorie à la pratique avancée
\n\nLes tests d'intégration sont une étape cruciale dans le développement de microservices en Go. Sans vérifier l'interaction avec les bases de données, les files d'attente de messages et les API externes, vous risquez de vous retrouver avec un "ça marche sur ma machine" dans sa manifestation la plus désagréable. Docker permet de créer des environnements propres et isolés pour chaque test, et les techniques avancées permettent de le faire rapidement, en parallèle et sans douleur. Dans cet article, nous allons décortiquer les approches réelles utilisées par les ingénieurs seniors.
\n\n1. Pourquoi Docker est le partenaire idéal pour les tests d'intégration en Go
\n\nGo se compile en un binaire statique, ce qui en fait un candidat idéal pour la conteneurisation. Lors des tests d'intégration, Docker résout trois problèmes clés :
\n- \n
- Isolation : chaque test reçoit une base de données ou un Redis frais, sans "déchets" des exécutions précédentes. \n
- Reproductibilité : les tests fonctionnent de la même manière sur la machine du développeur, dans le CI/CD et en production. \n
- Vitesse : des conteneurs correctement configurés se lancent en quelques secondes, pas en minutes. \n
L'erreur principale des débutants est de lancer un seul conteneur pour tous les tests. Cela entraîne des conditions de concurrence et des bugs "flottants". L'approche avancée : un conteneur par paquet de test, voire par test individuel.
\n\n2. Testcontainers pour Go : des conteneurs gérés depuis le code
\n\nLa bibliothèque testcontainers-go permet de gérer les conteneurs Docker directement depuis les tests Go. C'est plus élégant que d'écrire des scripts bash ou de dépendre d'un Docker Compose pré-lancé.
Installation :
\ngo get github.com/testcontainers/testcontainers-go\ngo get github.com/testcontainers/testcontainers-go/modules/postgres\n\nExemple : test de PostgreSQL avec migrations :
\npackage integration_test\n\nimport (\n "context"\n "database/sql"\n "testing"\n "time"\n\n "github.com/testcontainers/testcontainers-go"\n "github.com/testcontainers/testcontainers-go/modules/postgres"\n "github.com/testcontainers/testcontainers-go/wait"\n _ "github.com/lib/pq"\n)\n\nfunc TestUserRepository(t *testing.T) {\n ctx := context.Background()\n\n pgContainer, err := postgres.RunContainer(ctx,\n testcontainers.WithImage("postgres:16-alpine"),\n postgres.WithDatabase("testdb"),\n postgres.WithUsername("user"),\n postgres.WithPassword("password"),\n testcontainers.WithWaitStrategy(\n wait.ForLog("database system is ready to accept connections").\n WithOccurrence(2).\n WithStartupTimeout(30*time.Second),\n ),\n )\n if err != nil {\n t.Fatal(err)\n }\n defer pgContainer.Terminate(ctx)\n\n connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")\n if err != nil {\n t.Fatal(err)\n }\n\n db, err := sql.Open("postgres", connStr)\n if err != nil {\n t.Fatal(err)\n }\n defer db.Close()\n\n // Ici, exécuter les migrations et les tests\n _, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (\n id SERIAL PRIMARY KEY,\n name TEXT NOT NULL\n )`)\n if err != nil {\n t.Fatal(err)\n }\n\n // Tester le repository\n repo := NewUserRepository(db)\n user, err := repo.Create(ctx, "Alice")\n if err != nil {\n t.Fatal(err)\n }\n if user.Name != "Alice" {\n t.Errorf("expected Alice, got %s", user.Name)\n }\n}\n\nPoints clés :
\n- \n
WithWaitStrategy— on attend que PostgreSQL soit réellement prêt, pas seulement que le conteneur soit lancé. \n defer pgContainer.Terminate(ctx)— garantit le nettoyage même en cas de panique. \n - Chaque test reçoit sa propre instance de base de données — isolation totale. \n
3. Tests parallèles et gestion des ressources
\n\nGo supporte le parall