Pendant très longtemps, une des règles appliquées dans les projets informatiques étaient qu’un développeur ne devait jamais tester son propre code. Le code écrit, l’application créée, ne pouvaient être testés que par une équipe dédiée. Cette équipe, spécialisée dans les tests, n’avait pas nécessairement de connaissances techniques. Ce n’est qu’au début des années 90 que sont apparus des outils permettant aux développeurs de créer leurs propres tests, avant que l’on parle dans les années 2000 de pilotage par les tests (« Test Driven »). Enfin, le TDD (pour « Test Driven Development »), ou Développement Piloté par les Tests, est réellement codifié en 2003 par Kent Beck, et donnera naissance à plusieurs méthodologies. Voyons un peu ce que l’on trouve dans le TDD et ce que cette méthodologie agile peut apporter aux développements.
Vers une démocratisation des tests
A l’origine, il était entendu que le développement ou la programmation et les tests étaient deux choses très différentes, réservées à des métiers différents, axiome qui va être mis à mal par les pratiques TDD. Les développeurs créaient les programmes, qui étaient ensuite transmis à une équipe dédiée aux tests. Il n’était pas question qu’un développeur teste son propre code dans la mesure où le connaissant par cœur, il ne pouvait pas être objectif. En revanche, l’équipe de tests étant totalement indépendante, ce problème ne se posait plus. Globalement, il s’agissait d’une approche de type « boîte noire ». Les tests étaient effectués par des personnes n’ayant aucune connaissance du fonctionnement de l’application livrée.
Les tests sont souvent créés à l’aide d’outils qui enregistrent des séries d’actions (typiquement des clics dans l’interface utilisateur) et qui sont ensuite capables de les rejouer. Naturellement, pour que ces tests puissent être effectués, il faut que le code ait été écrit auparavant.
L’idée d’écrire les tests avant de développer le code fait son apparition dès 1996 avec l’apparition de l’eXtreme Programming, mais l’événement déclencheur est sans aucun doute la création et la publication de l’outil SUnit, pour le langage objet Smalltalk, créé par Kent Beck. SUnit est un framework de tests unitaires pour le langage Smalltalk. Par la suite, de nombreux outils de tests unitaires verront le jour comme JUnit pour le langage Java ou PHPUnit pour le langage PHP. Le développement piloté par les tests ou TDD va donc se répandre, et la création des tests va clairement passer des équipes dédiées aux équipes de développement.
Comment définir le TDD ?
Le TDD est une technique de développement mêlant intimement l’écriture des tests unitaires, la programmation et l’amélioration continue du code (encore appelée refactorisation).
En TDD, nous allons donc définir des tests unitaires. Mais comment définit-on un test unitaire ?
C’est assez simple en fait. Pour être unitaire, un test ne doit pas communiquer avec une base de données ni avec d’autres ressources ou systèmes d’informations sur le réseau, il ne manipule aucun fichier, il peut s’exécuter en même temps que les autres tests unitaires et il ne doit pas être lié à une autre fonctionnalité ou à un fichier de configuration pour être exécuté.
Chaque fonction unitaire de l’application possède son propre test unitaire, écrit avant le code. Le test est écrit dans un premier temps pour échouer. Le développeur s’assure ainsi en écrivant le test des conditions de réussite, mais aussi d’échec, de la fonction. Puis le développeur écrit la fonction demandée, de façon à ce que le test réussisse. Le code source produit pour réaliser la fonction demandée doit alors être le plus simple et le plus réduit possible, de façon à juste réaliser ce qui est demandé, pour que le test réussisse. Une fois le code source écrit et fonctionnel, une dernière étape de refactorisation permet de vérifier qu’il répond bien aux critères de simplicité demandés.
Ce cycle d’écriture va donc se répéter lors de la programmation de chaque fonction de l’application, augmentant ainsi le nombre de tests unitaires, et ce que l’on appelle la « couverture du code source ». Plus la couverture du code, c’est-à-dire plus il y a de tests qui couvrent les fonctionnalités programmées, et plus la qualité globale de l’application sera élevée.
L’une des difficultés pour le développeur est de déterminer la bonne granularité d’un test. Un test à « gros grain » vérifiera une fonctionnalité de façon assez large, sans entrer dans les détails de son fonctionnement. Un test à « grain fin » en revanche portera sur un aspect beaucoup plus précis, sur une partie de l’algorithme utilisé.
Au fur et à mesure que le développement d’une application va avancer, les tests unitaires vont donc se multiplier. Impossible donc de les exécuter un par un, cela prendrait rapidement un temps considérable. L’utilisation de tests unitaires est donc très liée à des outils de la famille « xUnit » : JUnit pour Java, PHPUnit pour PHP… Ces outils permettent de lancer l’ensemble des tests unitaires de façon automatique et de fournir un rapport d’exécution. Typiquement, si tous les tests unitaires sont passés, on obtient un résultat « vert ». Si un seul des tests échoue, on obtient un résultat « rouge ». L’objectif est donc qu’à chaque modification du code, l’ensemble des tests puisse être exécuté, et que l’on obtienne le feu vert de l’outil. Cela indique que les modifications effectuées non seulement ont passé les tests écrits à cette occasion, mais également qu’elles n’ont provoqué aucune régression par rapport au fonctionnement de l’application.
Quels avantages apporte le TDD ?
Le principal avantage du TDD est une baisse significative des anomalies constatées lors du développement d’une application. Le fait de s’obliger à passer tous les tests suffisamment fréquemment durant la phase de développement oblige les développeurs à être particulièrement vigilants et rigoureux. Le moindre test qui échoue alors qu’il passait auparavant permet de détecter une régression, qui peut être immédiatement analysée et corrigée. La refactorisation permanente du code permet sont amélioration et une plus grande qualité au final.
L’autre avantage est d’obtenir une application globalement mieux conçue. En effet, le fait de devoir écrire les tests avant le code source oblige le développeur à affiner la conception de son code objet. Le code produit présente ainsi une meilleure qualité et un plus grand découplage (c’est-à-dire que chaque partie est moins dépendante des autres).
Naturellement, la conception et la création des tests, tout comme leur exécution, demande du temps. Le TDD présente donc un surcoût par rapport à une phase de développement classique (écriture de code sans tests unitaires). Néanmoins, ce surcoût est rapidement compensé par le temps gagné au final en vérifications et en tests à la fin du projet. Plus une anomalie est détectée tôt durant le processus de développement, et moins le coût de sa correction sera élevé.
Pour conclure sur les pratiques TDD
Si le TDD présente un surcoût en développement, il est largement rattrapé par la qualité du code fourni à la fin du projet. La couverture du code en termes de tests unitaires peut donner un indice probant sur la qualité globale de l’application (et donc au final déterminer la difficulté ou la facilité à la maintenir ou à la faire évoluer à l’avenir). Si une couverture très élevée du code ne signifie pas automatiquement une grande qualité (encore faut-il que les tests soient bien conçus et écrits), une couverture inférieure à 80% en TDD est insuffisante et synonyme de problèmes futurs.
Des solutions comme Nutcache permettent d’intégrer facilement le TDD et la gestion des tests unitaires à votre projet. N’hésitez pas à profiter de la période gratuite de 14 jours pour tester Nutcache.