Question de Florent ce matin en arrivant à la BNP : « TDD en vrai ? ça marche ? à quoi cela sert-il ? ».
Bonne idée je me dis : réfléchissons un peu sur la démarche de développement pilotée par les tests, ou Test Driven Development (TDD).
Tout d’abord un constat : au BarCamp la semaine passée c’était l’un des sujets discutés, même si finalement 3 personnes sur 10 avaient réellement mis en place TDD au jour le jour. Je tiens à signaler que je n’ai pas d’avis sur la question. Je serai plutôt attentiste que suiveur.
TDD c’est quoi ?
Le Développement piloté par les tests est une pratique de développement qui préconise d’écrire les tests unitaires avant d’écrire le code. Nous en avons eu une démonstration brillante au Paris JUG en juin dernier.
Le cycle préconisé par TDD comporte cinq étapes :
1. écrire un premier test ;
2. vérifier qu’il échoue (car le code qu’il teste n’existe pas), afin de vérifier que le test est valide;
3. écrire juste le code suffisant pour passer le test,
4. vérifier que le test passe,
5. puis refactoriser le code, c’est-à-dire l’améliorer tout en gardant les mêmes fonctionnalités.
La critique
Je m’appuie sur un billet écrit en anglais « Cram your test driven development up your ass… » dont voici un résumé des critiques de la méthode TDD telle que l’auteur l’explique (note : j’ai traduis et librement interpreté l’article) :
DEBUT DU RESUME
Développement de code en avance, souvent sur-développement (Extra, often useless, up-front development)
L’ensemble des développements suit un cycle d’écriture de code basé sur ce que veut le client, en partant d’un prototype jusqu’à atteindre le code final que le client accepte. La transition d’une étape à l’autre entraine de la réécriture de code et donc, entraine l’inutilité du code initial, qui reste à l’état de prototype. Peu de développeurs peuvent se vanter d’écrire dès le premier coup une architecture complète et parfaite. C’est faux. Le client ajoute ou retire des demandes, vous butez sur une difficulté et vous changez de librairie, c’est la vie d’un développeur. En conséquence, l’auteur de l’article suggère que les TDD en forçant la pratique de l’écriture des tests dès le départ, sont inefficaces car ces tests seront effacés lorsque le code évoluera.
Le développement avec des visières rétrécit la vision du développeur
Encore une fois l’auteur explique que de même que les chevaux de courses portent des visières pour courir droit, les TDD tendent à restreindre le regard du développeur. Le risque : à trop se focaliser sur les tests unitaires, on en oublie le reste. Le résultat est que l’on tend à accepter une solution juste correcte pour se coller à ses tests, et que l’on ne s’oriente pas vers la meilleur solution. C’est d’autant plus vrai si les tests représentent beaucoup plus de lignes de code que le code testé.
Les tests pèsent plus que le code testé, et sont plus importants
Ce que veut dire l’auteur : après avoir écrit 500 lignes de tests, vous sentez vous capable de tout casser pour reprendre une autre idée ? Vraiment ?
A passer beaucoup de temps sur les tests, n’est-ce pas se détourner de l’objectif initial qui est avant tout d’écrire une application ? (note : je ne partage pas l’avis de l’auteur)
S’enfermer dans un coin
L’importance que l’on donne aux tests unitaires apporte le risque de croire que le test unitaire fait tout, et qu’en conséquence on adapte le code de la classe principale au test. Même pire, l’importance que l’on donne aux tests nous fait croire que les tests sont la vérité absolue, alors que l’on peut aussi écrire un test unitaire complétement faux. Des tests unitaires avec des bugs, cela existe.
L’application limite des TDD
Il est admis que les TDD s’appliquent difficilement à certains développements comme les développements d’interface utilisateur ou les interfaces Webs. Faut-il mobiliser toute son énergie pour mettre absolument des tests unitaires, difficile à maintenir, pour dire que l’on a fait des tests unitaires ?
Les tests résolvent des problèmes qui n’existent pas
TDD n’empêche pas l’apparition des Bugs. C’est une bêtise que de penser que les TDD empêchent l’apparition de bugs, car c’est ignorer les traitements fonctionnels et les cas limites. Une fois un bug trouvé, il est intéressant d’écrire un test unitaire afin de s’assurer que le bug ne va pas réapparaitre. Mais que se passe-t-il si un développeur en fixant un bug, casse complétement les tests unitaires ? Qui a raison ? le test unitaire initial ou le développeur qui corrige un bug ?
La couverture des tests unitaires
Une partie de la pratique TDD consiste à diviser en petits tests facilement testables des gros tests. Le problème est qu’à diviser en petits éléments, on test trop bas et donc sur un focus bien trop bas.
Inefficace
Si une partie de code doit être réécrite avec des tests unitaires pilotés par le développement, et qu’il faut très souvent changer les tests unitaires pour travailler sur une partie, n’est-il pas plus efficace d’écrire le code complétement puis de le tester à posteriori ?
Il est impossible d’estimer le temps de développement
Un planning précis ne peut être mis en place que si l’on a une idée assez précise de la quantité de travail à effectuer. Les TDD ajoutent une surcharge au développement qui représente plus de code à écrire que la classe développée elle-même. Si c’est envisageable pour un projet open-source où la contrainte de temps est vague, est-il possible de faire du TDD chez un client au forfait ?
FIN DU RESUME
Retrouvez l’article original : http://ww2.samhart.com/node/125
Après cette première partie qui tire à boulet rouge sur TDD, voyons un peu si nous pouvons répondre par quelques contre-arguments. Je vous traduis en partie cet article qui répond directement à « Cram your test driven development… »
Premier argument: Développement de code en avance, inefficacité et trop de codes En effet il est facile de dire que la pratique TDD entraine l’écriture de code qu’il faudra maintenir, et que lorsque l’on travaille sur un prototype, cela revient à écrire du code pour rien.
Soyons honnête : j’ai vu souvent des parties de code qui n’évoluent pas ou peu. Et rappelons que l’on parle de tests unitaires : certaines classes finalement ne seront jamais réécrites. Donc réduire TDD à un machin qui entraine trop de code, c’est faux. Le prototype est évidemment loin de l’application finale, mais un Controller reste un Controller, et on peut tout à fait placer des tests et développer son application en s’appuyant sur la pratique TDD.
Deuxième argument: Development with blinders on, Tests are weighted more heavily than the code and Coding yourself into a corner
L’idée de ces arguments est de dire que des tests incorrects vous entraine vers une pente abrupte et que vous risquez de ne croire que vos tests en essayant de corriger votre code, là où des tests seraient faux. Faut-il des TDD de TDD ?
Je vous donne mon avis : encore une fois, écrire des tests unitaires est faussement facile. C’est une tâche qui mise dans les mains d’un développeur inexpérimenté risque d’entrainer encore plus de cauchemar.
Je vois la phrase sortir du bois :
« … le test ne passe pas, corrige ton code… »
alors que c’est peut-être :
« ton test pourri est complétement dépassé, mets le à jour et revient me voir quand tu l’auras fait »
TDD est une pratique intéressante car l’idée est de capturer dans des tests unitaires des postconditions souhaitées pour du code (NDLR: belle phrase qui n’est pas de moi). Le problème est qu’un test unitaire représente à l’instant où l’on écrite ce test, la vision du client. Pour peu que celui-ci change d’avis, il est évident qu’il faudra reprendre le test. En effet c’est un effort.
La conclusion du Touilleur
Mon avis sur la question : TDD est une pratique intéressante, à appliquer peut-être avec un peu de recul, en évitant de croire que TDD est le nouveau Saint-Graal qui va faire repousser vos cheveux. Chaque méthode a ses évangélistes. Laissez moi 10 minutes avec Scrum, je vais vous démontrer que je suis un grand intégriste prêt à essayer de vous convertir. TDD tombe dans le même panneau : pour peu que votre chemin croise un intégriste passionné de TDD, vous serez illuminé et perdu.
TDD c’est pourtant le gilet jaune de signalisation qu’il faudrait avoir dans sa voiture. A appliquer parfois sur certaines parties de votre code où il est clair, facile et intelligent de placer de solides échafaudages, les tests unitaires. Après tout c’est une gymnastique qui vise à écrire des tests unitaires « utiles » et intelligents.
Je pense enfin que pour bien écrire des tests unitaires il faut investir du temps pour apprendre toute la boîte à outil suivante :
– JUnit
– dbUnit http://dbunit.sourceforge.net/
– EasyMock http://www.easymock.org/
– Unitils http://www.unitils.org
Finalement je n’ai pas répondu à la question de florent :
« Connais-tu quelqu’un d’assez neutre pour parler de TDD et pour donner son avis, sans que son œil se mette à briller et qu’il tente de te convertir ? »
Je vous laisse répondre et donner votre avis sur la question.
On pourra en parler au prochain Paris JUG le 14 octobre sur OSGI, organisé par Xebia.
————————————————————————-
Références:
– « Cram your test driven development up your… » de Samuel Hart
– « That sounds unnecessarily uncomfortable » de Ben Tels
– Test Driven Development
– Définition de TDD sur Wikipedia
– Livre « The Art of Unit Testing »
Mon retour sur TDD que j’ai un peu pratiqué :
– TDD permet de produire des tests, chose que l’on ne fera pas forcément après avoir écrit du code par manque de temps ou fainéantise
– TDD permet d’influencer la façon d’écrire son code : le code produit est mieux testable (moins de ‘new’, moins de ‘static’…)
Mais j’aurai tendance à dire que tout tester, c’est s’encombrer de beaucoup de code à maintenir. J’ai déjà écrit des tests dont la taille en ligne était largement supérieure au code « utile ». Et donc les tests étaient potentiellement plus difficile à comprendre et donc à maintenir que le code réel.
J’ai vu aussi des tests « trop » poussés : test du controller, du service, du DAO, test d’intégration des ces trois là et test fonctionnel depuis l’IHM. Au final, une question qui revient souvent : est-ce que le test fonctionnel ne garantit pas à lui tout seul que la pile de code fonctionne ou non (selon la Story/Use Case) ?
Enfin, TDD est une bonne technique pour écrire du code meilleur, bien penser et fiable. Mais de là à dire que la méthode doit être appliquée pour écrire chacune des lignes de code d’un projet, je doute.
Tom
Et pourtant les tests sont la clé du refactoring et des méthodes agiles. Plus la couverture des tests est importante, plus vite un écart (bug) est constaté dans le comportement de l’application lors d’une modification du code.
Les méthodes agiles nous apprennent aussi que plus un bug est découvert tardivement, plus il coûte cher à corriger.
Donc oui faite des tests, c’est ce qui vous garantira de développer des logiciels de qualité.
Par contre, faire des tests ça a bien sûr, un cout en développement mais aussi en maintenance. Les tests doivent eux-aussi évoluer, être corrigé voire être refactoré quand le besoin s’en fait sentir. Cependant ce coût est normalement compensé par une meilleure qualité et donc une réduction des coût des phases avales.
Les tests unitaires peuvent aussi être une sonnette d’alarme concernant la classe testée. Si le test est pénible à écrire ou compliqué à initialiser, etc .. c’est un « bad smell » sur la classe elle-même et devrait amener le développeur à se poser les bonnes questions : Quel est le rôle de ma classe (doit être unique le plus souvent) ? Pourquoi ai-je autant de dépendances (collaborators) à initialiser ?
Eventuellement prévoir dans ce cas un redesign de la classe.
Un autre rôle peu cité des tests unitaires : documenter et illustrer un exemple d’utilisation de la classe testée et aider les nouveaux développeurs à comprendre le code.