« MSI: 61% » : c’est la ligne que produisait Infection à chaque exécution manuelle, sans qu’aucune décision n’en découle depuis son installation huit mois plus tôt dans une extension de gestion d’abonnements. L’outil tournait, le chiffre s’affichait, et rien ne se passait ensuite : ni seuil fixé, ni intégration à la pipeline, ni discussion en équipe sur ce que ce nombre signifiait vraiment.
Un score de mutation mesure la capacité d’une suite de tests à détecter des modifications volontaires introduites dans le code, appelées mutants : un opérateur de comparaison inversé, une condition supprimée, une valeur de retour modifiée. Un score élevé indique que la suite réagit à ces altérations ; un score faible révèle des tests qui passent quel que soit le comportement réel du code, ce qui les rend décoratifs plutôt que protecteurs.
Pourquoi un score mesuré sans seuil ne sert à rien
Un chiffre affiché sans conséquence devient rapidement un chiffre ignoré. Sans seuil fixé, aucune régression du score ne déclenche d’alerte, et rien n’empêche le score de continuer à baisser silencieusement à mesure que du code nouveau s’ajoute sans tests suffisamment précis. La mesure seule, sans mécanisme de contrôle, ne change aucun comportement.
Fixer un seuil réaliste à partir du score mesuré, pas d’un chiffre théorique
Viser d’emblée un score de quatre-vingt-dix pour cent sur une base de code où le score mesuré est de soixante et un pour cent condamne le seuil à être perçu comme inatteignable, et pousse souvent à le contourner plutôt qu’à l’atteindre honnêtement. La méthode retenue fixe le seuil initial légèrement en dessous du score mesuré, pour ne bloquer que les régressions futures sans exiger de rattrapage immédiat sur l’existant :
{
"source": {
"directories": ["inc"]
},
"minMsi": 58,
"minCoveredMsi": 75,
"logs": {
"text": "infection.log"
}
}
Le paramètre minMsi porte sur l’ensemble du code source, y compris les portions non couvertes par des tests. Le paramètre minCoveredMsi porte uniquement sur le code déjà couvert, et constitue souvent le levier le plus pertinent à court terme : il pousse à améliorer la qualité des tests existants avant même d’en écrire de nouveaux.
Rendre le seuil bloquant progressivement, module par module
Plutôt que d’appliquer le seuil à l’ensemble du projet d’un coup, ce qui bloquerait immédiatement toute contribution touchant une zone historiquement mal testée, le seuil s’active module par module, en commençant par les zones critiques pour l’activité :
- Module de facturation : seuil bloquant activé immédiatement, score déjà satisfaisant.
- Module de gestion des abonnements : seuil bloquant activé après un mois de stabilisation.
- Module d’export de rapports, peu critique : seuil mesuré mais non bloquant pour l’instant.

Distinguer un mutant tué et un mutant ignoré
Infection classe certains mutants comme « ignorés » lorsqu’ils touchent du code jugé équivalent quel que soit le résultat, par exemple une modification qui n’affecte que la performance sans changer le comportement observable. Confondre un score élevé grâce à de nombreux mutants ignorés avec un score élevé grâce à des tests réellement efficaces conduit à une fausse confiance. Le rapport détaillé, généré avec l’option --only-covered, distingue précisément ces catégories :
vendor/bin/infection --only-covered --min-msi=58 --threads=4
| Résultat du mutant | Signification | Action |
|---|---|---|
| Tué | Un test a détecté la modification | Aucune, c’est l’objectif |
| Survivant | Aucun test n’a réagi au changement | Ajouter ou renforcer une assertion |
| Ignoré | Jugé sans impact observable | Vérifier que c’est réellement le cas |
Réagir concrètement à un mutant survivant
Un mutant survivant pointe précisément la ligne modifiée sans effet sur les tests, ce qui rend le correctif direct : si un mutant remplace $total >= $seuil par $total > $seuil sans faire échouer aucun test, un cas limite testant exactement l’égalité au seuil manque à la suite, et son ajout suffit à faire remonter le score sur ce point précis.
Un score de mutation ne remplace jamais la couverture de code, il la complète en posant une question différente : la ligne exécutée par un test l’est-elle vraiment vérifiée, ou seulement traversée sans conséquence sur l’assertion ?
En résumé
Un score de mutation n’a de valeur que rendu bloquant, mais un seuil fixé sans tenir compte du score réel existant décourage plus qu’il ne protège. Partir du score mesuré, l’activer progressivement module par module, et distinguer soigneusement mutants tués, survivants et ignorés : cette méthode transforme Infection d’un outil décoratif en un véritable garde-fou de qualité.