Aller au contenu
Mutation testing : mesurer ce que valent vos testsLecture : 0 %

Mutation testing : mesurer ce que valent vos tests

La couverture dit quelles lignes sont exécutées, pas lesquelles sont vérifiées. Ce que le mutation testing mesure réellement, ce qu'il coûte en temps machine et en attention, et les zones du code où il rapporte.

Par Elias Varen7 min de lectureMembres

Soit une règle métier et son test, avec une couverture de lignes et de branches de 100 %.

PHP
final class DiscountPolicy
{
    public function rateFor(int $quantity): int
    {
        if ($quantity >= 10) {
            return 15;
        }

        return 0;
    }
}

// Dans la classe de test
public function testDiscount(): void
{
    $policy = new DiscountPolicy();

    self::assertSame(15, $policy->rateFor(50));
    self::assertSame(0, $policy->rateFor(1));
}

Si l'on remplace >= par >, les deux assertions passent toujours. Si l'on remplace 10 par 11, ou par 20, elles passent encore. Le seuil, c'est-à-dire la seule chose que cette règle décide, n'est vérifié par rien. Un client qui commande exactement dix unités peut perdre sa remise sans qu'aucun test ne bouge, et votre tableau de bord affiche 100 %.

La couverture mesure ce que les tests exécutent. Le mutation testing mesure ce qu'ils remarquent. Ce sont deux grandeurs différentes, et seule la seconde a un rapport avec la raison pour laquelle on écrit des tests.

Tué, survivant, non couvert : le mécanisme

L'outil (Infection pour PHP, Stryker pour TypeScript) applique au code une petite modification à la fois, appelée mutant. Il inverse un opérateur de comparaison, remplace + par -, change true en false, supprime un appel de méthode, vide un tableau retourné, retire un return anticipé. Pour chaque mutant, il lance les tests.

mutant généré ──▶ aucun test n'exécute la ligne     → non couvert
              ──▶ un test échoue                     → tué
              ──▶ tous les tests passent             → survivant
              ──▶ les tests ne terminent pas         → timeout

Un mutant tué prouve qu'au moins un test dépend du comportement modifié. Un survivant est une modification du code que votre suite accepte sans broncher, ce qui signifie qu'un test manque, qu'une assertion est trop faible, ou que le code modifié ne sert à rien.

Le score est le rapport des mutants tués sur le total, et il a deux variantes qu'il ne faut pas confondre. Calculé sur tous les mutants, il mélange les trous de couverture et la faiblesse des assertions ; calculé sur les seuls mutants couverts, il isole la seconde. C'est cette variante qui apporte une information que vous n'aviez pas déjà.

Mutation testing : mesurer ce que valent vos tests · Deepstack