mercredi 2 janvier 2013

TDD dans le mur


Le développement dirigé par les tests ou TDD est reconnu comme faisant partie de l'état de l'art du développement logiciel : on ne connaît pas mieux actuellement. Il est pourtant facile de dévoyer cette pratique en l'appliquant avec un zèle malicieux, ce que j'appelle TDD dans le mur.

TDD consiste à organiser son travail de développement par cycles. Le développeur écrit un petit test le menant vers la solution, puis écrit le plus petit code possible capable de satisfaire le nouveau test ainsi que tous les précédents, puis éventuellement le développeur refactore son code afin de l'améliorer. Ce cycle composé de petits pas que l'on appelle des baby steps aide à la qualité du code mais aussi à son design. II permet de se concentrer sur une fonctionnalité à la fois, de créer le design au plus près du besoin. 

Une bonne pratique pour une première étape de TDD est de poser l'interface d'une méthode mais de coder en dur une implémentation qui répond de manière adhoc au test. Si on prend pour exemple une fonction calculant les termes d'une suite de Fibonacci, un premier test pourrait être de vérifier que le cinquième terme est 3. Une implémentation possible est de renvoyer directement la valeur 3 sans faire le moindre calcul. Cela peut paraître idiot de prime abord mais c'est parfaitement justifiable. Ce premier pas permet déjà de poser l'interface de la fonction, il est en cela utile. La valeur codée en dur est une réponse parfaitement valide au test, qui plus est vraiment la plus simple possible. Il serait contre productif à ce stade de commencer l'implémentation complète puisqu'on ne dispose pas encore des tests pour la valider. Il vaut mieux ajouter des tests et y répondre jusqu'au moment ou un refactoring permettra d'introduire l'algorithme. À ce moment, les tests précédemment créés serviront à valider l'algorithme.

Il ne faut pas trop poursuivre par des implémentations adhoc aux tests, au risque de transformer le code d'implémentation en véritable zone de guérilla ou l'on ne fait que camoufler sous des subterfuges l'inaptitude de l'implémentation à réellement fonctionner. Pour implémenter Fibonacci, un développeur malicieux pourrait par exemple utiliser une Map associant les bonnes réponses aux termes de la série de Fibonacci testés. Le code serait assez compact et tous les tests seraient au vert sans qu'un terme hors des tests puisse fonctionner. On fait alors du TDD dans les formes mais en allant droit dans le mur.

Où mettre la limite entre réponse adhoc et implémentation chiadée ? À chaque baby step, le test comporte un cas concret qui vise à valider une intention. Cette intention est exprimée dans le titre du test et doit ressembler à un élément de spécification de l'implémentation. On peut fixer la limite à cette intention : le code d'implémentation ne doit pas seulement vérifier le test, il doit également implémenter sincèrement l'intention. Si il ne le fait pas de suite, il devra le faire plus tard à l'occasion d'un refactoring.

crédit photo : http://theamazingios6maps.tumblr.com/post/32038256407/the-amazing-road-to-nowhere

2 commentaires:

  1. Article d'autant plus interessant que c'est souvent cette implémentation "ad hoc" qui bloque les nouveaux venu dans le monde du TDD.

    Le coté "implémentation naïve" est souvent dur à apréhender tant qu'on a pas compris que l'essentiel de cette méthode se passe dans la partie "refactor", et non pas dans la partie "green".

    Comme tu le dis, le tout est de bien connaitre l'intention. Le BDD nottament répond pas mal à ce problème, en assoicant un ensemble de scénario (cas de test) à une feature (qui décrit l'intention général).

    Pour moi c'est une bonne façon de trouver la limite entre réponse adhoc et implémentation chiadée.

    Aprés on peut pousser le débat plus loin: comment savoir si l'intention est vraiment valide par rapport à la fonctionnalité souhaité? CAD: dois-je implémenter une suite de fibonnacci si au final on ne l'utilise que pour les 5 premières valeurs?

    RépondreSupprimer
  2. Bonjour,
    Pour ma part j'utilise les critères suivants pour savoir ou m'arrêter:
    - N'y a t'il plus de redondance? Si je code la même valeur à la fois dans le test et dans le code, c'est une forme de redondance.
    - Ne suis-je pas en train de re-coder l'algo dans mes tests?
    Du coup, je préfère tester ma suite de fibonacci avec un tableau de 20 valeurs en dur et coder l'algo dans l'implémentation même si cela ne teste pas toutes les valeurs.
    #++
    Benoit

    RépondreSupprimer