Test driven development

Le Test-Driven Development (TDD), ou développement piloté par les tests en français, est une méthode de développement de logiciel qui consiste à concevoir un logiciel par petites étapes, de façon progressive, en écrivant avant chaque partie du code source propre au logiciel les tests correspondants et en remaniant le code continuellement.

Les cycles du TDD

Les trois lois du TDD

À l'origine, il s'agissait simplement d'écrire les tests avant de coder, et cette méthode s'appelait le test-first design. Puis elle évolua vers une granularité de développement plus fine : pour une ligne de test en échec, une ligne de code de production est écrite, dont l’objectif est de passer le test avec succès. Afin d'entériner cette granularité de ligne à ligne entre code de test et code de production, trois lois ont été élaborées.

Les trois lois telles que définies en 2008
Dénomination Exposé français Exposé VO Note d'interprétation
  • Loi no 1
"On ne doit pas écrire de code de production tant qu’on n’a pas écrit un test unitaire qui échoue." ("You may not write production code until you have written a failing unit test."[1]) c'est-à-dire qu'il n'est permis d'écrire du code de production que si un test unitaire est en échec[2].
  • Loi no 2
"On ne doit pas écrire plus d'un seul test unitaire qui échoue, et ne pas compiler revient à échouer." ("You may not write more of a unit test than is sufficient to fail, and not compiling is failing."[1]) c'est-à-dire qu'il n'est permis d'écrire qu'un nouveau test unitaire en échec à la fois, et un test unitaire qui ne compile pas est déjà un test en échec[2].
  • Loi no 3
"On ne doit pas écrire plus de code de production que nécessaire pour que le test unitaire actuellement en échec réussisse." ("You may not write more production code than is sufficient to pass the currently failing test."[1]) c'est-à-dire qu'il n'est permis d'écrire que du code de production permettant directement de faire passer le test unitaire précédent, ni plus ni moins[2].

Ces lois connaissent différentes variantes très similaires entre 2005 et 2008 (2005[2], 2006[3], 2007[4] et 2008[1]), toujours écrites par Robert C. Martin : elles sont ici énoncées selon les termes utilisés en 2008[1]. Or l'emploi de l'expression "test unitaire" dans ces lois prête ici largement à confusion. Par test unitaire, ces lois ne désignent pas un cas de test complet, mais en fait une unique assertion de test. Le cas de test complet devrait être obtenu après avoir itéré plusieurs fois l'ensemble de ces 3 lois[pas clair], sachant que la résolution d'une erreur de compilation constitue déjà une itération à part entière. En 2014, Robert C. Martin reformule d'ailleurs les trois lois de TDD[5] et le terme « unitaire » disparaît.

Une formulation exacte et exempte de toute ambiguïté de ces 3 lois serait plutôt la suivante.

Sans le terme "unitaire" et sans ambiguïté
Dénomination Formulation
  • Loi no 1
Il faut écrire un test qui échoue avant d’écrire le code de production correspondant.
  • Loi no 2
Il faut écrire une seule assertion à la fois, qui fait échouer le test ou qui échoue à la compilation.
  • Loi no 3
Il faut écrire le minimum de code de production pour que l'assertion du test actuellement en échec soit satisfaite.

On fait ainsi la distinction entre l'assertion à l'origine de l'échec du test et le test qui échoue lors de son exécution. De plus, on ne présume pas de la forme de test la plus adéquate, ni ne se restreint à une seule possible, ce qui est d'autant plus pragmatique. Le fait de mettre bout à bout les trois lois du TDD en une seule itération constitue ce qui est appelé un nano-cycle de TDD. À noter que ces trois lois ne couvrent que les conditions à respecter en TDD pour arriver à un test qui réussit, en exprimant le minimalisme attendu du test en échec.

Processus cyclique de développement

Une représentation graphique du cycle de la méthode de développement piloté par les tests (TDD)

Le processus préconisé par le TDD comporte cinq étapes :

  1. Écrire un seul test qui décrit une partie du problème à résoudre ;
  2. Vérifier que le test échoue, autrement dit qu'il est valide, c'est-à-dire que le code se rapportant à ce test n'existe pas ;
  3. Écrire juste assez de code pour que le test réussisse ;
  4. Vérifier que le test passe, ainsi que les autres tests existants ;
  5. Remanier le code, c'est-à-dire l'améliorer sans en altérer le comportement.

Ce processus est répété en plusieurs cycles, jusqu'à résoudre le problème d'origine dans son intégralité. Ces cycles successifs de développement sont appelés des micro-cycles de TDD.

Intérêt

Les tests tels qu'ils sont mis à profit dans le TDD permettent d'explorer et de préciser le besoin, puis de spécifier le comportement souhaité du logiciel en fonction de son utilisation, avant chaque étape de codage. Le logiciel ainsi produit est tout à la fois pensé pour répondre avec justesse au besoin et conçu pour le faire avec une complexité minimale. On obtient donc un logiciel mieux conçu, mieux testé et plus fiable, autrement dit de meilleure qualité.

Quand les tests sont écrits après le codage, comme c'est le cas traditionnellement, les choix d'implémentation contraignent l'écriture des tests : les tests sont écrits en fonction du code, et si certaines parties du code ne sont pas testables, elles ne seront pas testées. Au contraire, en testant avant de coder, on utilise le code avant son implémentation, de sorte que les contraintes définies par les tests s’imposent à l'implémentation : le code est écrit en fonction des tests. Le fait d'écrire les tests avant le code en TDD conduit donc à des implémentations testables : faciles à tester et testables entièrement. Or la testabilité du code favorise une meilleure conception par un couplage lâche et une cohésion forte, ce qui évite des erreurs de conception courantes.

Comme chaque test correspond à des modifications du code minimales, un test unique permet de faire un lien évident entre une régression et sa cause s'il échoue. Ce lien fait de l'exécution des tests un moment crucial dans un cycle de TDD : on capture l'état instantané du logiciel et on repère d'éventuelles régressions à la suite du dernier changement. En effet, en cas de régression, il faut absolument éviter de modifier le code, ce qui empêcherait de savoir si les tests échouent à cause du dernier changement ou d'un changement antérieur. Ainsi, les tests déjà écrits constituent un filet de sécurité contre les accidents de parcours, où l'on perdrait le lien entre changement et régression. Cette sécurité permet d'envisager avec sérénité n'importe quelle modification du code, qu'il s'agisse d'une transformation (modification qui affecte le comportement) ou d'un remaniement (modification qui n'altère pas le comportement), ainsi que les livraisons du logiciel de version en version.

L'objectif des remaniements réguliers du code est de réaligner la conception du code avec les besoins connus, afin de lutter contre l'entropie logicielle et de prévenir la dette technique. Changer la conception du code sans en altérer le comportement nécessite de disposer de tests qui garantissent l'absence de régression. Pour cela, on devrait faire appel à différentes formes complémentaires de tests, de manière à ne pas les réécrire à chaque remaniement de code, quelle que soit l'étendue des remaniements : tantôt des tests pour vérifier les résultats exacts d'un traitement, tantôt des tests pour vérifier que des composants collaborent correctement, sans que ces différents tests échouent ensemble pour les mêmes raisons.

Comme les tests sont écrits avant le code, ils jouent plusieurs rôles dans le TDD : ils servent d'abord à résoudre un problème en guidant le codage à chaque étape, ils fournissent ensuite une sécurité contre les régressions et, enfin, ils documentent le comportement du logiciel. Grâce à son utilisation des tests, le TDD fait gagner en productivité de plusieurs façons.

  • Le TDD permet d'éviter des modifications de code sans lien avec le but recherché, car on se focalise à chaque étape sur la satisfaction d'un besoin précis, en conservant le cap du problème d'ensemble à résoudre.
  • Le TDD permet d'éviter les accidents de parcours, où des tests échouent sans qu'on puisse identifier le changement responsable, ce qui aurait pour effet d'allonger la durée d'un cycle de développement.
  • Le TDD permet de maîtriser le coût des évolutions logicielles au fil du temps, grâce à une conception du code perméable au changement.
  • Le TDD permet de s'approprier plus facilement n'importe quelle partie du code en vue de le faire évoluer, car chaque test ajouté dans la construction du logiciel explique et documente le comportement du logiciel en traduisant l'intention des auteurs.
  • Le TDD permet de livrer une nouvelle version d'un logiciel avec un haut niveau de confiance dans la qualité des livrables, confiance justifiée par la couverture et la pertinence des tests à sa construction.

La programmation binomiale en TDD

Lorsque deux personnes s'associent en binôme pour résoudre un problème de programmation, elles occupent tour à tour deux rôles semblables à ceux d'un équipage de rallye automobile : le pilote, qui tient le clavier, code, tandis que le copilote supervise, prend du recul et guide son pilote par étapes ; les rôles sont échangés à intervalles réguliers. En appliquant le TDD, un binôme peut échanger les rôles de différentes façons : soit entre deux cycles de développement au moment d'itérer, soit entre l'écriture d'un nouveau test en échec et le codage d'un nouveau comportement. La seconde façon d'échanger les rôles force à séparer les préoccupations du test de celles de l'implémentation et met le copilote à contribution pour écrire le test seulement, tandis que la première permet de dérouler un cycle complet en occupant un même rôle.

Notes et références

  1. (en) Robert C. Martin, Clean Code : A Handbook of Agile Software Craftsmanship, Upper Saddle River, NJ, Prentice Hall, , 431 p. (ISBN 978-0-13-235088-4), Chapter 9: Unit Tests, The Three Laws of TDD, p. 122
  2. Robert C. Martin, The Three Laws of TDD, 2005
  3. (en) Robert C. Martin et Micah Martin, Agile Principles, Patterns, and Practices in C#, Prentice Hall, , 768 p. (ISBN 0-13-185725-8), Chapter 4. Testing, p. 32
  4. Robert C. Martin, « Professionalism and Test-Driven Development », IEEE Software, vol. 24, no 3, , p. 32–36 (ISSN 0740-7459, DOI 10.1109/ms.2007.85, résumé, lire en ligne, consulté le )
  5. (en) Robert C. Martin, « The Cycles of TDD », sur The Clean Code Blog (consulté le )

Voir aussi

Articles connexes

Bibliographie

  • (en) Kent Beck, Test Driven Development : By Example, Addison-Wesley, , 240 p. (ISBN 0-321-14653-0, lire en ligne)
  • (en) Lasse Koskela, Test Driven : TDD and Acceptance TDD for Java Developers, Manning, , 470 p. (ISBN 978-1-932394-85-6 et 1-932394-85-0)

Liens externes

  • Portail de la programmation informatique
Cet article est issu de Wikipedia. Le texte est sous licence Creative Commons - Attribution - Partage dans les Mêmes. Des conditions supplémentaires peuvent s'appliquer aux fichiers multimédias.