Test Driven Development
Le développement piloté par les tests (TDD) est un cas particulier de programmation axée sur les tests qui ajoute l'élément de conception continue.
La programmation axée sur les tests (TDP) consiste à produire des tests unitaires automatisés pour le code de production. avant Vous écrivez le code de production. Au lieu d'écrire les tests après coup (ou, plus souvent, de ne jamais les écrire), vous commencez toujours par un test unitaire. Pour chaque petite portion de fonctionnalité du code de production, vous créez et exécutez d'abord un petit test (idéalement très petit) et ciblé qui spécifie et valide le comportement du code. Ce test peut même ne pas compiler au départ, car toutes les classes et méthodes requises n'existent peut-être pas. Néanmoins, il sert de spécification exécutable. Vous le faites ensuite compiler avec un minimum de code de production, afin de pouvoir l'exécuter et observer son échec. (Parfois, vous vous attendez à un échec, et il réussit, ce qui est une information précieuse.) Vous produisez ensuite exactement le code nécessaire pour que ce test réussisse.
Cette technique peut paraître étrange au premier abord à de nombreux programmeurs qui l'essaient. C'est un peu comme si des alpinistes gravissaient une paroi rocheuse en y plaçant des points d'ancrage au fur et à mesure. Pourquoi s'embêter autant ? Cela ne ralentit-il pas considérablement la progression ? La réponse est que cette approche n'a de sens que si l'on s'appuie fortement et régulièrement sur ces tests unitaires par la suite. Ceux qui pratiquent régulièrement le développement piloté par les tests affirment que ces tests unitaires compensent largement l'effort nécessaire à leur écriture.
Pour les développements basés sur les tests (test-first), on utilise généralement un framework de tests unitaires automatisés de la famille xUnit (JUnit pour Java, NUnit pour C#, etc.). Ces frameworks simplifient considérablement la création, l'exécution, l'organisation et la gestion de grands ensembles de tests unitaires. (Dans l'univers Java, en tout cas, ils sont de plus en plus intégrés aux meilleurs IDE.) C'est un avantage, car avec cette approche, on accumule un très grand nombre de tests unitaires.
Avantages du travail basé sur les tests préalables
Des ensembles complets de tests unitaires automatisés servent de filet de détection des bogues. Ils définissent avec précision et fiabilité le comportement actuel du système. Les équipes qui privilégient les tests constatent une réduction significative du nombre de défauts tout au long du cycle de vie du système et consacrent beaucoup moins de temps au débogage. Des tests unitaires bien écrits constituent également une excellente documentation de conception, toujours, par définition, synchronisée avec le code de production. Un avantage quelque peu inattendu : de nombreux programmeurs affirment que la petite barre verte indiquant que tous les tests s'exécutent correctement devient addictive. Une fois habitué à ces petits retours positifs fréquents sur la santé de votre code, il est très difficile de s'en passer. Enfin, si le comportement de votre code est parfaitement maîtrisé grâce à de nombreux tests unitaires de qualité, il est beaucoup plus sûr de le développer. safer pour vous refactoriser le codeSi une refactorisation (ou une optimisation des performances, ou tout autre changement) introduit un bug, vos tests vous alertent rapidement.
Développement piloté par les tests : aller plus loin
Le développement piloté par les tests (TDD) est un cas particulier de programmation axée sur les tests, auquel s'ajoute la notion de conception continue. Avec le TDD, la conception du système n'est plus contrainte par un document de conception papier. Au contraire, le processus d'écriture des tests et du code de production guide la conception au fur et à mesure. Toutes les quelques minutes, on refactorise pour simplifier et clarifier le code. Si l'on peut facilement imaginer une méthode, une classe ou un modèle objet entier plus clair et plus propre, on refactorise dans cette direction, protégé en permanence par une solide suite de tests unitaires. Le principe du TDD repose sur l'idée qu'il est impossible de déterminer la conception la plus adaptée tant qu'on n'est pas plongé au cœur du code. En apprenant ce qui fonctionne et ce qui ne fonctionne pas, on est dans les meilleures conditions pour appliquer ces enseignements, tant qu'ils sont encore frais dans la mémoire. Et toute cette activité est protégée par les suites de tests unitaires automatisés.
Vous pourriez commencer par une bonne partie de la conception initiale, bien qu'il soit plus courant de commencer par une conception assez simple. conception de code simpleDans le monde de la programmation extrême, quelques schémas UML sur tableau blanc suffisent souvent. Mais avec le TDD, la quantité de conception initiale importe moins que la marge de manœuvre qu'elle peut prendre pour s'en éloigner au fil du développement. On n'effectuera peut-être pas de changements architecturaux radicaux, mais on peut remanier considérablement le modèle objet si cela s'avère judicieux. Certaines entreprises ont plus de latitude politique que d'autres pour implémenter un véritable TDD.
Tests préalables vs débogage
Il est utile de comparer l'effort consacré à la rédaction des tests en amont au temps passé à déboguer. Le débogage implique souvent d'examiner de grandes quantités de code. L'approche « test-first » permet de se concentrer sur une portion de code plus petite, où les risques d'erreur sont moindres. Il est difficile pour les responsables de prévoir la durée réelle du débogage. D'une certaine manière, une grande partie de cet effort est gaspillée. Le débogage implique un investissement en temps, une mise en place et une infrastructure (points d'arrêt, surveillance des variables temporaires, instructions d'affichage) qui sont tous, en fin de compte, jetables. Une fois le bogue trouvé et corrigé, toute cette analyse est perdue. Et si elle n'est pas totalement perdue pour vous, elle l'est certainement pour les autres programmeurs qui maintiennent ou développent ce code. Avec l'approche « test-first », les tests sont disponibles pour tous, indéfiniment. Si un bogue réapparaît, le même test qui l'a détecté une première fois peut le détecter à nouveau. Si un bogue apparaît faute de test correspondant, vous pouvez écrire un test qui le détectera par la suite. C'est pourquoi de nombreux adeptes de l'approche « test-first » affirment qu'elle représente l'exemple parfait d'une approche plus intelligente, et non plus dure.
Techniques et outils de test préalable
Il n'est pas toujours aisé d'écrire un test unitaire pour chaque aspect du comportement d'un système. Qu'en est-il des interfaces graphiques ? Des EJB et autres composants gérés par des frameworks conteneurisés ? Des bases de données et de la persistance en général ? Comment vérifier qu'une exception est correctement levée ? Comment tester les performances ? Comment mesurer la couverture, la granularité et la qualité des tests ? La communauté du développement piloté par les tests (TID) apporte des réponses à ces questions grâce à un ensemble d'outils et de techniques en constante évolution. Une ingéniosité remarquable permet de couvrir chaque aspect du comportement d'un système avec des tests unitaires. Par exemple, il est souvent judicieux de tester un composant du système indépendamment de ses collaborateurs et des ressources externes, en utilisant des objets factices et simulés. Sans ces objets, vos tests unitaires pourraient ne pas parvenir à instancier l'objet testé. Ou, dans le cas de ressources externes comme les connexions réseau, les bases de données ou les interfaces graphiques, l'utilisation de la ressource réelle lors d'un test peut considérablement le ralentir, tandis que l'utilisation d'une version factice ou simulée permet de maintenir un fonctionnement rapide en mémoire. Et bien que certaines fonctionnalités puissent toujours nécessiter test manuel, le pourcentage pour lequel cela est incontestablement vrai continue de diminuer.