9. Implications
• Test = traduction des demandes métier dans le
code
• Validation du fonctionnement
• Ceinture de sécurité pour le refactoring
• 0 régression
• Design émergent
• On livre quand on veut
05/05/2015
19. Références
• http://en.wikipedia.org/wiki/Test-driven_development
• Un livre : Professional Test-Driven Development with C#
• http://fr.slideshare.net/brunoboucard/si-le-tdd-est-
mort-alors-mix-it Présentation de TDD par B. Boucard
et T. Pierrain (SGCIB) au dernier Mix-IT de Lyon
• http://googletesting.blogspot.co.uk/2015/04/just-say-
no-to-more-end-to-end-tests.html
• http://www.codewars.com pour trouver des exercices
• http://www.osherove.com en particulier la section Kata
• http://www.jbrains.ca/permalink/tdd-is-not-magic
05/05/2015
20. Merci !
Rendez-vous d’ici fin mai pour
pratiquer en .Net !
@GTechene
guillaume.techene@nexeo.fr
http://guillaume.techene.net/blog/
05/05/2015
Notes de l'éditeur
On veut un produit moderne qui réponde aux besoins actuels mais on utilise un process waterfall datant de la fin des années 60 en utilisant un modèle de management qui n’est pas adapté.
Le métier effectue beaucoup de demandes souvent mal priorisées, avec parfois un besoin mal défini.
Il met alors de la pression pour voir le livrable ASAP ; or, la gestion actuelle des projets fait que les livrables n’arrivent qu’à la toute fin voire après la deadline. Les tests, vus comme optionnels car n’apportant pas de plus-value au métier, sont décalés à la fin voire jamais faits du tout.
Les utilisateurs n’ayant pas été mis dans la boucle durant le process découvrent l’appli une fois “finie”. Ils font souvent des retours violents, comme telle ou telle fonctionnalité importante qui manque ou qui ne marche pas du tout comme elle devrait.
Du coup le dev se met à corriger en urgence et introduit des régressions et des bugs récurrents. L’absence de tests commence à se faire sentir.
L’architecture devient de plus en plus alambiquée et difficile à comprendre, ce qui favorise les nouvelles régressions.
Il est alors de plus en plus difficile de toucher au code sans casser quoi que ce soit à côté mais un refactoring est hors de question puisqu’il faudrait demander aux utilisateurs et BA de retester toute l’appli depuis le début pour vérifier les TNR blocage au niveau du budget qui crève le plafond.
En plus, la doc et les specs écrites au début (“quand on avait le temps”) ne correspondent plus à l’état du code.
Le code est monolithique, l’application bancale, les coûts sont monstrueux, tout le monde est mécontent.
Les commentaires sont trop souvent obsolètes. Au mieux ils ne servent à rien, au pire ils vont induire en erreur. Les rares commentaires utiles décrivent des comportements métier très pointus et difficiles à comprendre pour un non-initié (ex : grosse formule de maths). Mais le risque lié à l’obsolescence subsiste.
Au début d’un développement, on commence par coder les tests unitaires. Pas de code avant, le test doit planter.
“Red, Green, Refactor” :
Red : Le test plante.
Green : On écrit juste le code nécessaire au passage du test, ce qui évite d’introduire du code inutile et un design potentiellement lourd.
Refactor : On met le code au propre (si nécessaire).
Une fois la feature finie, on commite. On peut même commiter une fois que le test passe. Et on recommence.
Le fait d’écrire les tests permet de traduire le besoin métier en code ; on s’assure donc dès le départ de bien comprendre les specs. On a des tests qui valident un fonctionnement on peut donc refactorer le code testé quand on veut sans craindre les régressions à gogo.
Le design de l’application se fait petit à petit, on part d’une base simple pour évoluer ensuite. On pourra en effet refactorer facilement, donc l’archi peut évoluer sans crainte. C’est un point majeur, car on évite de stagner dans une archi inadéquate, souvent source de bien des maux (contournements, régressions, etc…).
Lorsqu’on a un bug, soit un ou des tests plantent et on peut cibler facilement ce qui ne va pas, soit on n’a qu’à ajouter le cas de test ensuite pour prévenir le bug de resurgir ensuite.
On peut livrer au métier quand on veut puisque les tests passent. Cela ne signifie pas que l’appli est finie mais qu’une partie est livrable et testable en l’état. On peut donc mettre le métier dans la boucle plus vite et avoir des feedbacks plus rapprochés. L’itération et l’incrémentation sont au coeur du process : on est “Agile”.
Le TDD concerne essentiellement les tests unitaires. Il faut bien faire attention à rester très précis et bien cibler ce que l’on teste. Un test d’intégration n’a pas lieu d’être à ce stade, car il couvre trop de périmètre et ne permet pas l’approche itérative citée précédemment, sans parler du temps et des ressources nécessaires au lancement de ce type de test.
Le test unitaire a un but technique, il ne servira que peu aux BA ou utilisateurs. Contrairement au test d’intégration ou au test end-to-end qui valideront eux un véritable comportement métier.
Les interactions avec le métier sont fréquentes : il est donc impératif 1) de savoir communiquer et 2) que le métier soit OK avec ça ! Si le métier ne veut pas fonctionner dans ce mode, soit il faut avoir de bons BA, soit le projet est compromis.
Pour des tests unitaires, on veut une granularité très fine. Il est donc indispensable de découper chaque demande métier en plusieurs tâches, elles-mêmes divisées en plusieurs parties. Chaque partie fera l’objet de plusieurs tests visant à vérifier le comportement pour plusieurs données en entrée.
Afin de bien développer l’application et de la rendre testable, on a besoin de s’appuyer sur les principes SOLID, notamment S et D. Voir http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29
Evidemment, créer une batterie de tests unitaires n’est utile que si ces tests sont rejoués régulièrement… d’où le besoin d’un environnement d’intégration continue avec usine de build et lancement automatique des tests.
On procède petit à petit ! Notion de “baby steps”.
Un bon test unitaire :
Ne teste qu’une seule chose à la fois : une seule classe, une seule méthode
Isole le code de l’environnement et du contexte, quitte à fournir des implémentations tronquées ou bidon des dépendances (“mocking”). Sinon on tombe dans le test d’intégration et on est hors scope.
S’exécute rapidement car il est lancé souvent.
Teste aussi les cas à la marge pour éviter de ne tester que le “happy path” qui est le cas idéal où tout se déroule parfaitement. Si une méthode prend une chaîne en paramètre, il faut aussi penser à tester le cas où la chaîne est null ou vide.
Il est difficile de commencer par coder un test qui va appeler du code qui n’existe pas encore. Ce n’est pas naturel… et c’est normal ! Mais l’habitude vient vite, il suffit de pratiquer.
Du coup, quand on code, on a souvent envie d’aller plus loin que de répondre simplement au test. Oui mais cela signifie qu’on implémente un besoin pas demandé par le métier ou qu’on est en train de complexifier l’architecture pour rien… Il faut savoir résister à cette envie et faire simplement ce qui a été demandé. KISS : Keep It Simple, Stupid (Fais Simple, Gros Malin :p)
Il faut le support de tout le monde. Sans l’aval du manager, les outils ne seront peut-être pas là et tout le monde ne fonctionnera pas sur le même mode. Donc impossible de livrer de façon incrémentale au métier. Si le métier ne suit pas, la communication sera difficile et les specs pas ou mal respectées. A noter quand même qu’on n’a pas forcément besoin de specs détaillées ou d’outils perfectionnés pour commencer à faire les tests simples…
En premier lieu, les frameworks de tests unitaires sont un passage obligé. Ils diffèrent selon les langages et les équipes de dev mais en maîtriser un ou deux est fortement recommandé. Idem pour le mocking et tout ce qui est injection de dépendances (http://en.wikipedia.org/wiki/Dependency_inversion_principle).
En termes d’outils, il existe beaucoup de plug-ins aux divers environnements de dev qui facilitent la création et le suivi de tests. Certains sont même fournis avec le framework de test (ex : nUnit) ou carrément avec l’IDE.
Les katas, comme dans les arts martiaux, représentent des exercices simples dont le but est d’assimiler une ou plusieurs techniques. C’est pareil en dev : en réalisant un exercice régulièrement en se tenant au TDD, on va finir par bien comprendre les concepts derrière.
Quand je suis entré en finance, on m’a dit : “tu vas voir, en finance ils ont environ 10 ans de retard sur la gestion des projets informatiques”. Ca se vérifie aujourd’hui. Le problème c’est qu’en tant que consultant, il faut savoir se tenir à la page.
TDD est un process important, ce n’est pas un effet de mode, il est là pour rester. Il est donc crucial de le maîtriser sous peine de devenir un dino à moyen terme.
L’avantage c’est que ce n’est pas si dur ; en 2 semaines à pratiquer pendant 30 minutes par jour sur des exercices simples, on connaît bien les rouages.
CodeWars est très sympa et une bonne source d’inspiration mais est orienté résultat : à vous de vous astreindre à utiliser le TDD lors de l’élaboration de la solution.