Test-Driven Development, By Example
Kent Beck est un développeur reconnu par ses pairs. Il fait parti des signataires du manifeste Agile et est le fondateur de l’eXtreme Programming qui met en avant des valeurs telles que le courage, le retour sur développement (feedback) et amène de nouvelles méthodes de travail telles que le pair-programming et le développement piloté par les tests (test-driven development).
C’est justement cette dernière technique qui est détaillée dans ce livre. Je l’ai trouvé très bien écrit; il se lit vite du fait de chapitres courts, voire très courts (2 pages pour le chapitre 7), et du style fluide de Kent Beck. L’enchaînement de chapitres courts donnent envie de ne pas s’arrêter, surtout quand ils décrivent une histoire comme c’est le cas pour la première partie qui est un exemple, avec code, du développement d’un système monétaire.
La deuxième partie m’a moins convaincu. Elle est plus difficile à lire et le développement d’un framework de test en Python ne m’a pas emballé.
Enfin la dernière partie est un vrai guide pour mettre en place un développement piloté par les tests. Il fournit beaucoup de trucs et astuces, explique la philosophie qui fait le succès de cette méthode et surtout Kent Beck nous dévoile un peu sa vision du développement logiciel.
Je ne vais pas m’attarder sur les deux premières parties qui sont des déroulements d’exemples concrets mais plutôt reprendre en détail la troisième partie : Patterns for Test-Driven Development.
Chapitre 25 : Test-Driven Development Patterns. Kent Beck répond ici à quelques questions basiques que l’on peut se poser sur les tests: Qu’est-ce que l’on entend par tester ? Quand teste-on ? Comment choisir l’enchaînement logique à tester ? Comment choisir les données à tester ?
- Un test doit être automatisé. Les tests manuels ont tendance à être sacrifiés lorsque le développement rentre dans une phase de stress alors que c’est peut-être là qu’ils sont le plus vital.
- Les tests doivent être aussi indépendants que possible afin que l’échec d’un test ne mette pas en péril l’ensemble.
- Faire une liste des choses à tester avant de les écrire est un bonne pratique, de même que commencer à écrire les tests avant de développer du code de production.
- La première chose à écrire dans un test sont les assertions, ensuite on peut écrire le code nécessaire à l’obtention des objets utilisés dans celles-ci, cela permet de fixer le(s) but(s) du test.
- Il faut choisir des données faciles à comprendre et qui ont un sens logique. On écrit des test aussi pour qu’ils soient lus par d’autres développeurs.
Chapitre 26 : Red Bar Patterns. Quand écrit-on des tests, où les écrit-on et quand faut-il arrêter d’en écrire ?
- Quel test doit-on choisir dans la liste ? Il n’y a pas de réponse absolue. Il faut choisir celui qui nous paraît le plus évident à mettre en œuvre, le plus accessible à l’instant donné.
- Avec quel test démarrer ? Avec celui qui met en œuvre une variante de l’opération qui ne fait rien, un constructeur vide par exemple.
- Les tests sont un bon moyen de comprendre un système et d’en discuter avec les membres de son équipe.
- Les tests sont aussi un bon moyen d’apprendre comment marche une bibliothèque externe en écrivant du code client de cette bibliothèque et en s’assurant que l’on obtient bien ce que l’on attend.
- Lorsqu’une nouvelle idée apparaît, lors d’une discussion technique par exemple, ajouter un test à la liste le décrivant et revenez à vos moutons.
- Lorsqu’un défaut (bug) apparaît, une bonne pratique est de coder le plus petit test possible mettant en exergue le problème et d’avoir comme but de le faire passer au vert.
- Il est important de savoir s’arrêter quand on se sent fatigué, le code écrit dans ces moments-là est rarement de qualité.
- Que faire quand vous êtes perdu dans votre code ? Mettez-le de côté et repartez de zéro, une nouvelle approche sera sûrement meilleure.
- Quel environnement nécessaire au TDD ? Le plus important est un excellent fauteuil ! quitte à avoir un bureau bon marché.
Chapitre 27 : Testing Patterns. Techniques de tests un peu plus détaillées.
- Que faire lorsqu’un cas test (test case) devient tros gros ? Le scinder en plusieurs petits.
- Comment tester un objet qui repose sur des ressources complexes ou coûteuses en mettre en place ? Écrire un “mock“ qui simule le comportement attendu en répondant par des constantes (valeurs “en dur”).
- Comment s’assurer que l’ordre des messages appelés est correct ? Ajouter un “logger” dans la communication afin de tracer l’échange.
- Comment tester le code de gestion d’erreur ? Utiliser un “mock“ qui déclenche l’erreur plutôt qu’un objet “réel” qui serait difficile à pousser à l’erreur.
- Comment arrêter une session de codage quand vous êtes seul contributeur ? Arrêtez-vous sur un test en erreur, cela permet de se remémorer plus rapidement le contexte lorsque que vous reprenez.
- Comment arrêter une session de codage quand vous faites parti d’une équipe ? Ne vous arrêtez, ou “commitez“, que lorsque les tests sont verts.
Chapitre 28 : Green Bar Patterns. Une fois le test écrit et qu’il est au rouge, comment le faire passer au vert ?
- Faussez-le jusqu’à avoir la bonne version ! Écrivez un code simpliste qui renvoie le résultat obtenu et raffinez la façon d’obtenir le résultat.
- La triangulation est aussi une technique qui fait ses preuves. Elle consiste à écrire une première assertion (ex: 3+1=4), à écrire le code le plus simple voire simpliste répondant au problème (ex : return 4) et à rajouter une assertion (ex: 3+4=7) qui permet de trianguler et ainsi d’écrire le code correct (ex : return a + b).
- Comment implémenter les opérations simples ? Simplement en les implémentant.
- Comment implémenter des opérations qui travaillent sur des collections d’objets ? Implémentez-les d’abord sur une instance et intégrez la notion de collection après.
Chapitre 29 : xUnit Patterns. De l’utilisation d’un framework de la famille xUnit.
- Comment vérifier que les tests fonctionnent correctement ? Écrivez des expressions booléennes qui s’assurent de la véracité du code écrit et faites les évaluer automatiquement par des assertions.
- Comment mettre en commun du code de création d’objets ? Écrivez des “fixture“ qui rassemble la construction d’objets dans la méthode setUp() et la destruction dans tearDown().
- Comment représenter un test ? Par une méthode dont le nom commence par “test” et qui fait des assertions.
- Comment tester une exception attendue ? Interceptez l’exception et ignorez-la. Passez en erreur dans la suite du code si elle n’a pas été interceptée.
- Comment lancer tous les tests ? En les rassemblant dans une suite.
Chapitre 30 : Design Patterns. Plusieurs patrons de conception sont utilisés dans le TDD que je ne vais pas reprendre ici. Voici ceux que Kent Beck détaille dans ce chapitre : “Command”, “Value Object”, “Null Object”, “Template Method”, “Pluggable Object”, “Pluggable Selector”, “Factory Method”, “Imposter”, “Composite”, “Collecting Parameter”, “Singleton (à ne jamais utiliser selon lui :) C’est pas faux ! )”
Chapitre 31 : Refactoring. Ou comment modifier le design d’un système, parfois radicalement, par des petites étapes.
- Comment réconcilier des parties de code semblables ? En les faisant converger pas à pas et en ne les fusionnant que lorsqu’elles sont exactement identiques.
- Comment ne modifier qu’une fraction d’une méthode ou d’un objet ? En l’isolant avant toute modification afin de réduire les effets de bords.
- Comment migrer d’une représentation de données vers une autre ? En dupliquant temporairement les données et en supprimant l’ancienne version ensuite.
- Comment simplifier une méthode pour la rendre plus lisible ? En la divisant en plusieurs méthodes plus petites (“Extract method”).
- Comment faire lorsque l’on est perdu dans l’enchaînement des méthodes ? En rassemblant toutes les petites méthodes appelées dans une seule (“Inline method”). Une fois la méthode comprise, la rediviser de façon plus intuitive par la technique “Extract method”.
- Comment introduire une nouvelle implémentation d’un code existant ? En extrayant une interface de la classe existante et en la faisant implémenter par le code existant et par la nouvelle implémentation (“Extract interface”).
- Comment représenter une méthode compliquée qui prend de nombreux paramètres et de nombreuses variables locales ? En faisant un objet (“Methode object”).
Chapitre 32 : Mastering TDD. Dans ce dernier chapitre, Kent Beck expose quelques questions sur la philosophie de la programmation par les tests. Certaines questions ont des réponses courtes, d’autres longues et certaines questions restent ouvertes, simplement posées et nous amène à réfléchir par nous-mêmes.