Tests de Mutation en PHP : Comment écrire des tests qui détectent vraiment les bugs

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : Pourquoi 100% de couverture de code est une illusion ?

\n

Avez-vous déjà été fier d'un rapport PHPUnit affichant « Code Coverage : 98% » ? Et ensuite, vous avez déployé une version, et un bug critique est apparu en production. Cela vous dit quelque chose ? Le problème est que la couverture classique (line/statement coverage) mesure uniquement quelles lignes de code ont été exécutées. Elle ne vérifie pas si vos tests testent réellement la logique.

\n

Imaginez que vous ayez écrit un test qui vérifie qu'une fonction retourne true. Et que la fonction elle-même retourne toujours true, même si on la casse. Le test réussira, la couverture sera de 100%, mais vous n'aurez pas détecté le bug. C'est exactement là qu'intervient le mutation testing en PHP — une technique qui bouleverse la conception habituelle des tests.

\n

Dans cet article, nous allons voir ce qu'est le test de mutation, comment il fonctionne avec un exemple de code PHP réel, et comment l'intégrer dans votre projet avec la bibliothèque Infection.

\n\n

Qu'est-ce que le Mutation Testing ? (Et pourquoi ce n'est pas de la magie)

\n

Mutation testing (ou test de mutation) est une méthode d'évaluation de la qualité des tests qui consiste à introduire de petites modifications intentionnelles (mutations) dans le code source. Si après la modification le test échoue — le mutant est tué, le test fonctionne. Si le test reste vert — le mutant a survécu, ce qui signifie que votre test ne vérifie rien.

\n

Une analogie simple : vous êtes un gardien, et vos tests sont des caméras. Le mutation testing envoie des « intrus » (mutants) dans le bâtiment. Si la caméra (le test) remarque l'intrus — elle fonctionne. Si l'intrus se promène tranquillement — la caméra est inutile.

\n

En PHP, l'outil principal pour cela est Infection — une bibliothèque qui exécute PHPUnit sur chaque mutant et analyse les résultats.

\n\n

Comment cela se présente-t-il en pratique ?

\n

Supposons que vous ayez une classe de calculatrice simple :

\n
<?php\nclass Calculator\n{\n    public function add(int $a, int $b): int\n    {\n        return $a + $b;\n    }\n}
\n

Et son test :

\n
<?php\nuse PHPUnit\\Framework\\TestCase;\n\nclass CalculatorTest extends TestCase\n{\n    public function testAdd()\n    {\n        $calc = new Calculator();\n        $result = $calc->add(2, 3);\n        $this->assertEquals(5, $result);\n    }\n}
\n

L'outil Infection créera un mutant en remplaçant + par - :

\n
return $a - $b;
\n

Ensuite, il exécutera le test. Si le test échoue — le mutant est tué. Si le test réussit (et il réussira, car vous ne vérifiez que 2+3, pas 2-3) — le mutant a survécu. C'est un signal : votre test n'est pas assez bon.

\n\n

Installation d'Infection et premier lancement

\n

Pour commencer, installons Infection via Composer :

\n
composer require --dev infection/infection
\n

Ensuite, créez un fichier de configuration infection.json (ou exécutez vendor/bin/infection pour une configuration interactive).

\n

Exemple de configuration minimale :

\n
{\n    "source": {\n        "directories": [\n            "src"\n        ]\n    },\n    "phpUnit": {\n        "configDir": "."\n    },\n    "mutators": {\n        "@default": true\n    }\n}
\n

Lancez le test de mutation :

\n
vendor/bin/infection --min-msi=100
\n

Où --min-msi=100 signifie que nous voulons que 100% des mutants soient tués (un objectif idéal, mais rarement atteignable). Infection générera un rapport au format HTML (dossier infection.html), où les mutants survivants seront marqués en rouge.

\n\n

Types de mutations en PHP (avec exemples)

\n

Infection prend en charge des dizaines de mutateurs. Voici les plus courants que vous rencontrerez :

\n\n

1. Opérateurs mathématiques

\n

Remplacement de + par -, * par /, % par *, etc.

\n
// Code source\nreturn $price * $quantity;\n\n// Mutant\nreturn $price / $quantity;

Blogs

Book Recommendations