Une extension affichait fièrement 92 % de couverture de code sur son module de calcul de frais de port, un chiffre rassurant en apparence. Pourtant, un bug avait été livré en production : un opérateur > remplacé par erreur par un >= dans une condition de seuil de franco de port. Les tests existants appelaient bien la fonction concernée, ce qui suffisait à compter la ligne comme « couverte », mais aucun d’entre eux ne vérifiait le comportement exactement à la frontière du seuil. Le mutation testing existe justement pour révéler ce genre de faux sentiment de sécurité.
Le principe d’Infection consiste à modifier volontairement le code source — inverser une condition, changer une constante, supprimer un appel — puis à relancer la suite de tests sur cette version « mutée ». Si au moins un test échoue, le mutant est « tué » : les tests ont détecté l’anomalie. S’il survit, c’est-à-dire si tous les tests passent malgré le bug injecté, cela révèle une zone insuffisamment vérifiée.
Installer et configurer Infection sur une extension existante
Infection s’installe via Composer et nécessite un fichier de configuration pointant vers le code à muter et la commande de test à utiliser :
composer require --dev infection/infection
# infection.json5
{
source: {
directories: ["src"]
},
logs: {
text: "infection.log"
},
mutators: {
"@default": true
}
}
vendor/bin/infection --threads=4
Le paramètre --threads parallélise l’exécution, indispensable dès que le projet dépasse quelques centaines de mutants : chaque mutant nécessite de relancer tout ou partie de la suite de tests, ce qui rend le mutation testing nettement plus lent que la simple mesure de couverture.
Lire le score MSI
Le Mutation Score Indicator (MSI) résume le pourcentage de mutants tués par rapport au total de mutants générés. Contrairement au pourcentage de couverture, il mesure la qualité des assertions, pas seulement l’exécution des lignes :
Mutation Testing Score Indicator (MSI): 71%
Covered Code Mutation Score Indicator: 78%
Mutation Code Coverage: 91%

La ligne la plus révélatrice est souvent l’écart entre Mutation Code Coverage (91 %, proche de la couverture classique) et le MSI réel (71 %) : cet écart quantifie exactement le nombre de lignes exécutées par les tests sans être vérifiées par une assertion pertinente.
Renforcer les tests faibles à partir des mutants survivants
Le rapport détaillé liste chaque mutant survivant avec la ligne exacte modifiée. Sur la fonction de franco de port évoquée en introduction, Infection aurait généré un mutant remplaçant > par >= et l’aurait signalé comme survivant :
- Repérer le mutant survivant dans le rapport HTML (
vendor/bin/infection --logger-html=report.html) - Écrire un test qui couvre précisément la valeur limite (le montant exact du seuil, pas seulement un montant au-dessus et un en dessous)
- Relancer Infection sur ce fichier seul pour confirmer que le mutant est désormais tué
Ne pas viser 100 % de MSI à tout prix
Certains mutants sont « équivalents » : ils modifient le code sans changer son comportement observable, et ne pourront jamais être tués par aucun test. Poursuivre un MSI de 100 % sur un projet WordPress complet consomme un temps disproportionné pour un gain marginal. L’approche la plus rentable consiste à cibler Infection sur les modules critiques — calcul de prix, permissions, validation de données — plutôt que sur l’ensemble du code.
Notre verdict
Infection ne remplace pas la mesure de couverture, il la complète en répondant à une question différente : vos tests détecteraient-ils un vrai bug s’il était introduit demain ? Sur un module sensible, un MSI de 70 % avec des mutants ciblés donne plus confiance qu’un pourcentage de couverture de 95 % sans cette vérification. La mesure de couverture classique reste néanmoins l’outil à utiliser en premier pour repérer le code jamais exécuté du tout.