A propos de la couverture de code

Lorsque l’on met en place des tests sur un projet et que l’on souhaite commencer à avoir des indicateurs sur ces derniers, c’est naturellement que l’on se tourne sur le taux de couverture de code (alias le code coverage en anglais). Pourtant, dans absolument toutes mes expériences professionnelles, cet indicateur a systématiquement été critiqué par certains développeurs.

Effectivement, le taux de couverture de code est la mesure du nombre de lignes de code source exécuté durant les tests. En d’autres termes, il s’agit de compter les lignes du code de production parcourues durant les tests. Le problème étant qu’un test peut ne faire aucune vérification et pour autant avoir un taux de couverture de code élevé.

Prenons par exemple le code suivant:

function calcul(string $op, int $x, int $y): int
{
    return match ($op) {
        '+' => $x + $y,
        '-' => $x - $y,
        '*' => $x * $y,
        '/' => $x / $y,
        default => throw new \InvalidArgumentException(),
    };
}

Avec le test associé:

class CalculTest extends TestCase
{
    public function testCalcul(): void
    {
        $resultat = calcul('+', 1, 5);

        $this->assertIsInt($resultat);
    }
}

Avec un tel test, nous obtenons le résultat ci-dessous:

Summary:
  Classes: 100.00% (1/1)
  Methods: 100.00% (1/1)
  Lines:   100.00% (7/7)

Nous avons donc un test qui ne fait aucune vérification et pourtant nous avons un taux de couverture de code de 100%. C’est pour cela que cette métrique est décriée.

C’est aussi pour cela qu’il faut considérer la couverture de code comme un indicateur négatif d’un projet et non pas comme un indicateur de mesure de qualité. Un projet ayant une couverture de code basse peut indiquer un nombre insuffisant de tests sur le projet. Alors qu’à l’inverse, cela indique qu’il y a des tests, mais cet indicateur à lui seul, ne donne pas de mesure sur la qualité de ces derniers. Pour cela, il faudra le corréler avec d’autres métriques.

A noter qu’il y a également une seconde métrique corrélée au taux de couverture de code, mais dont on parle beaucoup moins. Il s’agit du taux de couverture de branche (ou branch coverage). On parle également du taux de couverture des chemins d’exécution qui comme son nom l’indique permet de vérifier si chaque parcours de code possible a été exécuté ou non.

Si l’on reprend notre exemple précédent avec le code effectuant les opérations de calculs et le test associé que nous avions écrit, on se retrouverait avec la couverture suivante:

Summary:
  Classes:  0.00% (0/1)
  Methods:  0.00% (0/1)
  Paths:    20.00% (1/5)
  Branches: 42.86% (3/7)
  Lines:    100.00% (7/7)

Ici on peut clairement voir la différence. Toutes les lignes de code ont été exécutées, mais les tests n’ont permis d’explorer qu’un seul des cinq chemins d’exécution possible. Cela indique clairement que notre test, s’il est pertinent, ne couvre que peu de comportements.

On peut donc voir le taux de couverture de branche comme un indicateur plus fiable que le taux de couverture de code. Néanmoins attention, le calcul de ce dernier est couteux en termes de ressource (temps, CPU, mémoire) et cela peut ralentir drastiquement votre suite de test.