Tests d'intégration : les pièges à éviter dans un legacy

Par KamangaFeb 20, 20268 mins de lecture

Tests d'intégration : les pièges à éviter dans un legacy

J'accompagnais une équipe dans un opérateur télécoms : des ingénieurs sérieux, déterminés à "enfin écrire des tests sur le legacy". Ils avaient décidé d'attaquer directement le moteur de facturation : 18 000 lignes de code, 30 dépendances, et une logique métier partiellement documentée. Quatre semaines plus tard, ils avaient produit 3 tests unitaires instables et une équipe découragée. Les tests ne compilaient pas parce que les dépendances ne pouvaient pas être initialisées dans un contexte de test. La motivation avait plongé. On a repris à zéro, mais différemment.

Décider d'ajouter des tests d'intégration à un legacy est une bonne décision. Mal exécutée, c'est une décision qui peut paralyser l'équipe pendant 3 mois pour un résultat décevant. Les pièges sont prévisibles et évitables.

Les tests unitaires sur le legacy ont une limite bien connue : ils testent des unités isolées mais ne testent pas le comportement d'ensemble. Pour un filet de sécurité au niveau de l'équipe, assurez-vous que votre Definition of Done exige des tests d'intégration sur les chemins critiques.. Un système peut avoir 70% de couverture unitaire et tomber en production parce que l'intégration entre composants n'est pas testée. Mais ajouter des tests d'intégration à un legacy n'est pas une opération simple. Le legacy n'a généralement pas été conçu pour être testé.


Pourquoi les tests unitaires seuls ne suffisent pas

Un test unitaire isole une fonction et vérifie son comportement en isolation. C'est nécessaire. Mais c'est insuffisant pour détecter les bugs qui apparaissent à la jonction entre composants.

Exemple concret : deux classes parfaitement testées en isolation, l'une qui sérialise une date en MM/DD/YYYY et l'autre qui la désérialise en attendant YYYY-MM-DD. Les tests unitaires passent. Le système plante en prod.

Les tests d'intégration testent les interactions : entre services, entre couches, entre la couche application et la base de données. Ils ont un coût d'exécution plus élevé mais une valeur de détection différente et complémentaire. Sur un legacy, les bugs les plus coûteux sont presque toujours des bugs d'intégration, pas des bugs unitaires. J'ai rarement vu un incident de prod majeur causé par un bug unitaire. J'en ai vu des dizaines causés par des bugs d'intégration silencieux.


Piège 1 : Tester sans comprendre le comportement attendu

La première erreur est de commencer à écrire des tests sans savoir ce que le système est censé faire. Sur un legacy réel, le comportement attendu est souvent partiellement documenté ou connu uniquement par des personnes qui ont quitté l'entreprise.

Ce qui arrive : l'équipe écrit des tests qui vérifient le comportement actuel, y compris les bugs existants. Quand un vrai bug est corrigé, les tests cassent. L'équipe corrige les tests pour "faire passer le build" au lieu de corriger le comportement. Les tests deviennent un obstacle plutôt qu'un filet de sécurité.

La sortie : avant d'écrire le premier test d'intégration, j'organise une session de 2 à 3 heures avec un expert métier pour définir les comportements de référence : quels sont les scénarios nominaux et les scénarios d'erreur qui doivent être garantis ? Cette session produit les spécifications des tests, pas l'inverse.


Piège 2 : Commencer par le code le plus complexe

L'instinct technique pousse à attaquer les modules les plus risqués en premier. C'est une mauvaise stratégie, je l'ai apprise à mes dépens dans mes premiers accompagnements.

Ce qui arrive : l'équipe passe 3 semaines à essayer de tester le module central du legacy, celui avec 10 000 lignes de code, 30 dépendances implicites, et une logique métier que personne ne comprend entièrement. Les tests ne compilent pas. L'équipe est bloquée et découragée.

La sortie : commencer par les modules en bordure du système, c'est-à-dire les points d'entrée et de sortie. Un test d'intégration qui vérifie qu'une API renvoie le bon status code pour une requête valide est simple à écrire et immédiatement utile. Une fois les tests de bordure en place, progresser vers le centre.

Retour sur l'équipe télécoms : après l'échec initial sur le moteur de facturation, j'ai proposé de pivoter vers les tests de bordure, à savoir les API REST qui alimentent le moteur. En 2 semaines, 15 tests d'intégration fonctionnels. Ce succès rapide a relancé la motivation et donné une base solide pour progresser vers le centre progressivement.


Vous essayez d'ajouter des tests à un legacy mais vous ne savez pas par où commencer ?

Vous avez déjà essayé, et soit l'équipe s'est découragée, soit les tests écrits ne donnent pas confiance. La séquence d'ajout de tests sur un legacy dépend de l'architecture, de la criticité des modules, et des ressources disponibles. En 30 minutes, on définit la stratégie adaptée à votre contexte.


Piège 3 : Créer une dépendance à la base de données de production

Pour tester l'intégration, il faut des données. L'approche la plus simple est de pointer sur la base de données de production (ou une copie) dans les tests. C'est une très mauvaise idée.

Ce qui arrive :

  • Les tests sont non-déterministes : ils dépendent de l'état des données, qui change
  • Les tests ralentissent avec le volume de données
  • Des tests mal écrits peuvent modifier des données réelles
  • La base de prod ne peut pas être réinitialisée entre les tests

La sortie : utiliser une base de test dédiée, initialisée à chaque exécution de test avec un jeu de données connu et contrôlé. Pour les bases relationnelles, les test containers (Testcontainers Java, Python, Node.js) permettent de démarrer une vraie base de données dans Docker pour chaque suite de tests : propre, isolée, et détruite après.

Pour les systèmes avec des données volumineuses, créer un dataset de test représentatif mais minimal (100 à 1 000 enregistrements couvrant tous les scénarios) plutôt que de copier des millions de lignes de prod.


Piège 4 : Ignorer les tests de régression comportementale

Le legacy a un comportement actuel. Ce comportement, même imparfait, est probablement attendu par les utilisateurs. Des tests d'intégration qui ne couvrent pas ce comportement actuel permettent des régressions silencieuses.

Ce qui arrive : l'équipe écrit de nouveaux tests d'intégration basés sur la documentation (incomplète). Des comportements non-documentés mais attendus par les utilisateurs sont cassés lors d'un refactoring. L'équipe l'apprend en production.

La sortie : les characterization tests d'abord. Avant d'écrire des tests qui vérifient le comportement attendu, écrire des tests qui documentent le comportement actuel. C'est le concept de Michael Feathers dans "Working Effectively with Legacy Code" : un characterization test capture ce que le système fait réellement, pas ce qu'il devrait faire.

Ces tests ont une propriété précieuse : si un refactoring casse un characterization test, il faut une décision explicite pour valider que le changement de comportement est intentionnel. C'est le filet de sécurité que je rends obligatoire avant toute modification significative d'un legacy.


La séquence qui fonctionne

  1. Session de définition des comportements de référence (2-3h avec expert métier) → liste des scénarios à garantir
  2. Audit des dépendances (1 jour) → cartographie des intégrations externes, DB, services tiers
  3. Infrastructure de test (2-3 jours) → test containers, fixtures de données, CI configurée
  4. Characterization tests sur les chemins critiques (1-2 semaines) → documentation du comportement actuel
  5. Tests d'intégration de bordure (2-3 semaines) → points d'entrée et de sortie du système
  6. Progression vers le centre (continu) → module par module, en priorisant par criticité

Cette séquence n'est pas rapide : comptez 6 à 10 semaines pour une base solide. Mais elle produit des tests qui tiennent dans le temps et qui augmentent réellement la confiance de l'équipe.


FAQ sur les tests d'intégration en legacy

1. Quelle est la différence entre un test d'intégration et un test end-to-end ?

Un test d'intégration vérifie l'interaction entre deux ou plusieurs composants internes du système (ex : service + base de données). Un test end-to-end vérifie un scénario utilisateur complet, de l'interface jusqu'à la persistance. Sur un legacy, je commence par les tests d'intégration : ils sont plus rapides, plus stables, et plus faciles à diagnostiquer que les tests E2E.

2. Combien de temps faut-il pour avoir une suite de tests d'intégration utile sur un legacy ?

Pour un legacy de taille moyenne (50 000 à 200 000 lignes de code), comptez 6 à 10 semaines pour avoir une suite couvrant les 10 à 15 scénarios critiques. C'est un investissement qui se rentabilise dès le premier refactoring majeur : une régression évitée sur un module critique vaut facilement 10 fois le coût de la suite de tests.

3. Faut-il des développeurs dédiés aux tests ou tous les développeurs participent ?

Tous les développeurs participent. Les tests d'intégration ne sont pas une spécialité, ils sont une responsabilité de l'équipe. En pratique, je désigne un "test champion" par équipe pour maintenir l'infrastructure et les standards, mais chaque développeur qui modifie un module doit ajouter ou mettre à jour les tests correspondants.

4. Comment gérer les tests qui tombent de façon intermittente (flaky tests) ?

Les flaky tests sont le cancer des suites de tests : ils réduisent la confiance dans tous les tests et poussent les équipes à ignorer les échecs. Ma règle est simple : un flaky test est soit corrigé dans les 48h, soit désactivé avec un ticket de suivi. Un test désactivé qui n'a pas de ticket actif dans le backlog est supprimé. La discipline sur les flaky tests conditionne la confiance dans toute la suite.

5. Les test containers ralentissent-ils la CI de manière inacceptable ?

Sur un pipeline bien configuré, non. Les test containers démarrent en 5 à 15 secondes pour une base PostgreSQL ou MySQL. En parallélisant les suites de tests et en utilisant un cache Docker sur la CI, l'impact sur le temps total est marginal. Le vrai coût est le premier setup : 1 à 2 jours pour configurer l'infrastructure. Après ça, chaque nouvelle suite de tests bénéficie de l'infrastructure existante.


Ressource gratuite : Engineering Maturity Self-Assessment

L'assessment inclut une évaluation complète de vos pratiques de tests : couverture, types de tests, intégration dans la CI. Identifiez vos angles morts et priorisez les améliorations à fort impact.


Ecris par Kamanga

Expert IT avec 25 ans d'expérience en développement logiciel, diplômé EPITECH et MBA. Spécialisé en software craftsmanship, gestion du changement, stratégie, direction des systèmes d'information, coaching et certifié en agilité.