Aujourd'hui, dans l'écosystème PHP, on ne se pose enfin plus la question de savoir ce qu'est un test unitaire. Les tests unitaires sont devenus une pratique courante, et il existe des frameworks de tests matures, comme PHPUnit ou atoum.
L'engouement pour la qualité logicielle a poussé la communauté à progresser et à proposer de nouvelles pratiques, de nouveaux outils... Cependant, il reste encore exceptionnel de contrôler la qualité des tests unitaires produits.
Attention, par contrôle de qualité des tests, je ne parle pas d'indicateurs de couverture de code. Non, car une couverture de code à 100 % est contre-productive, impossible et surtout totalement fausse. Et voici pourquoi.
Les outils de couverture actuelle ont un défaut majeur, inhérent à leur fonctionnement : ils indiquent uniquement si des portions de code ont été exécutées lors des tests unitaires.
Or, prenons un exemple simple :
et le test unitaire suivant :
Le premier appel de bar() affiche 'AD', le second affiche 'BC'. Je suis donc passé partout dans mon code source, la couverture de code de ma suite de test est de 100 % :
Super ! 100 % ! J'ai couvert tout mon code ! En plus c'est vert, c'est donc que tout va bien.
Et bien non… ou plutôt, je suis passé à chaque nœud de mon code source, mais je n'ai pas couvert tous les chemins possibles de code. Car avec ces deux if(), le code peut faire :
Quatre chemins possibles donc, alors que la couverture de code est de 100 % en à peine deux tests. La couverture de code est donc un indicateur assez peu fiable, puisqu'elle nous trompe allègrement : dans mon exemple, très simple, je n'ai en réalité couvert que 50 % des chemins possibles ; et encore, uniquement pour cette portion de code. Et le code est très simple : imaginez un switch() avec des if() imbriqués, vous verrez qu'en réalité la différence entre la réalité et la couverture de code indiquée est exponentielle.
Attendez, j'ai donc ici une couverture de code de 100 %, mais, en réalité, mes tests unitaires ne font strictement rien. Il n'y a même pas d'assertion !
Il arrive en effet très fréquemment que les tests unitaires ne servent à rien. Oui, même dans la vraie vie, même sur de gros projets. C'est encore plus vrai lorsqu'on commence à " sur-mocker " tout et n'importe quoi : un mock par-ci, un mock par-là... Il m'arrive de voir des tests unitaires où les assertions portent sur des mocks ; autant ne pas écrire de test unitaire.
Vous l'avez compris, il est difficile d'obtenir des indicateurs fiables de qualité pour des tests unitaires. Et encore, on ne cherche que des indicateurs...
C'est ici qu'intervient le Mutation Testing. Dès les années 70 (oui, c'est vieux), s'est posée la question de savoir comment résoudre cette question de la qualité de tests.
L'idée du Mutation Testing consiste à introduire des bugs dans le code source, puis à vérifier si les tests unitaires ont bien détecté ces bugs. Autrement dit, nous introduisons des mutations dans le code source (on parle de " mutants "), les tests unitaires sont sensés les détecter (on dit alors que le mutant en question est " tué ").
Si tous les bugs ont bien été détectés, c'est sans doute que les tests unitaires sont fiables. Si aucun bug n'est détecté, les tests unitaires ne servent à rien. Dire que tous les mutants ont été tués est donc un indicateur de la bonne qualité de tests unitaires.
Les avantages du Mutation Testing sont nombreux :
Par contre, le Mutation Testing a un certain nombre d'inconvénients :
Maintenant que les bases sont posées, parlons outils. Il n'existait en PHP, à ma connaissance, qu'un seul outil pour le Mutation Testing : Mutagenesis. Malheureusement, cet outil, pourtant très intéressant, a, malgré ses avantages, trois inconvénients :
Partant de ce constat, j'ai réfléchi à une solution pour proposer un outil plus moderne et moins lourd. J'ai donc démarré le développement de MutaTesting. A titre personnel, je dois avouer que ça m'intéressait beaucoup de voir comment résoudre les difficultés inhérentes à cet outil sans devoir passer par l'installation de RunKit ou de xDebug.
MutaTesting, c'est quoi ? C'est un outil PHP qui crée des mutants à partir votre code source puis lance vos tests unitaires pour voir s'il est possible de tuer ces mutants.
Mon idée première a été de faire un outil très simple : pas besoin d'extension PHP, pas besoin de configuration compliquée ; il suffit, en ligne de commande, d'indiquer trois choses :
Par exemple, pour une suite de tests PHPUnit :
ou pour atoum :
C'est tout. A partir de là, MutaTesting va procéder à un certain nombre de processus :
Bien entendu, votre code source n'est jamais modifié. En réalité, l'outil joue avec un StreamWrapper spécifique pour le flux de fichier standard (file://) pour substituer la mutation à votre code originel.
Voici quelques exemples de bugs qui peuvent être introduits :
Voici le résultat de la mutation pour les tests de MutaTesting même :
L'analyse du code source a donc permis de créer 46 mutants, donc 26 ont survécu. Le score des tests est donc de 43 %.
Les tests unitaires ont donc des anomalies. Relançons le même outil pour obtenir un compte rendu au format HTML, plus complet (option –format=html). Voici un aperçu de ce qui est obtenu :
Cette fois-ci on a plus de détails : les mutants sont détaillés et regroupés par fichier de source.
Ce qui est intéressant, c'est que les sources qui ressortent comme les moins bien testées sont justement celles que je n'ai pas développées par TDD. L'analyse de ce rapport va donc me permettre de me focaliser sur les tests unitaires qui concernent les sources sur lesquelles le plus de mutations a survécu.
Le Mutation Testing est donc finalement assez simple : on introduit des bugs dans un code source, puis on vérifie qu'ils sont bien détectés par les tests unitaires.
Ce qui est surtout intéressant c'est que cette pratique ne nécessite aucun développement en plus, mais simplement l'utilisation d'un outil, automatisé
Concernant MutaTesting même, que j'ai développé et qui est disponible sur Github, je pense que c'est un outil que j'espère simple à utiliser et à étendre. Bien que fonctionnel, il peut largement être amélioré. Les Pull Requests sont les bienvenues :-) .
De la même façons, vos retours sur les bienvenus : sur vos pratiques, vos attentes en terme d'outils...si l'envie vous prend de l'essayer, voire d'y contribuer.
PS: j'ai eu pas mal de remarques sur la lisibilité du thème de ce blog. J'ai changé de thème ; c'est mieux ? :p
© Jean-François Lépine, 2013 - 2024