Test Driven Development

Le développement piloté par les tests (TDD) est un cas particulier de programmation test-first qui ajoute l'élément de conception continue.

La programmation test-first consiste à produire des tests unitaires automatisés pour le code de production, avant vous écrivez ce code de production. Au lieu d'écrire des tests par la suite (ou, plus généralement, de ne jamais écrire ces tests), vous commencez toujours par un test unitaire. Pour chaque petit morceau de fonctionnalité dans le code de production, vous créez et exécutez d'abord un petit (idéalement très petit) test ciblé qui spécifie et valide ce que le code fera. Ce test peut même ne pas se compiler, au début, car toutes les classes et méthodes dont il a besoin n'existent peut-être pas. Néanmoins, il fonctionne comme une sorte de spécification exécutable. Vous le faites ensuite compiler avec un code de production minimal, afin que vous puissiez l'exécuter et le voir échouer. (Parfois, vous vous attendez à ce qu'il échoue et qu'il réussisse, ce qui est une information utile.) Vous produisez alors exactement la quantité de code qui permettra à ce test de réussir.

Cette technique semble étrange, au début, à quelques programmeurs qui l'essayent. C'est un peu comme des grimpeurs qui escaladent une paroi rocheuse, plaçant des ancres dans le mur au fur et à mesure. Pourquoi se donner tout ce mal ? Cela vous ralentit sûrement considérablement? La réponse est que cela n'a de sens que si vous finissez par vous fier fortement et à plusieurs reprises à ces tests unitaires plus tard. Ceux qui pratiquent le test d'abord affirment régulièrement que ces tests unitaires rapportent plus que l'effort nécessaire pour les écrire.

Pour le travail de test en premier, vous utiliserez généralement l'un des frameworks de tests unitaires automatisés de la famille xUnit (JUnit pour Java, NUnit pour C #, etc.). Ces frameworks facilitent la création, l'exécution, l'organisation et la gestion de grandes suites de tests unitaires. (Dans le monde Java, du moins, ils sont de plus en plus bien intégrés dans les meilleurs IDE.) C'est bien, car lorsque vous travaillez en testant d'abord, vous accumulez de très nombreux tests unitaires.

Avantages du travail de test en premier

Des ensembles complets de tests unitaires automatisés servent de sorte de filet pour détecter les bogues. Ils fixent, de manière précise et déterministe, le comportement actuel du système. Les bonnes équipes qui testent d'abord constatent qu'elles obtiennent beaucoup moins de défauts tout au long du cycle de vie du système et passent beaucoup moins de temps à déboguer. Des tests unitaires bien écrits constituent également une excellente documentation de conception qui est toujours, par définition, en phase avec le code de production. Un avantage quelque peu inattendu : de nombreux programmeurs rapportent que « la petite barre verte » qui montre que tous les tests fonctionnent proprement devient addictive. Une fois que vous êtes habitué à ces petits retours positifs fréquents sur la santé de votre code, il est vraiment difficile d'y renoncer. Enfin, si le comportement de votre code est cloué avec beaucoup de bons tests unitaires, c'est beaucoup safer pour vous de refactoriser le code. Si un refactoring (ou un ajustement des performances, ou tout autre changement) introduit un bug, vos tests vous alertent rapidement.

Développement piloté par les tests : aller plus loin

Le développement piloté par les tests (TDD) est un cas particulier de programmation test-first qui ajoute l'élément de conception continue. Avec TDD, la conception du système n'est pas contrainte par un document de conception papier. Au lieu de cela, vous autorisez le processus d'écriture des tests et du code de production à orienter la conception au fur et à mesure. Toutes les quelques minutes, vous refactorisez pour simplifier et clarifier. Si vous pouvez facilement imaginer une méthode, une classe ou un modèle d'objet entier plus clair et plus propre, vous refactorisez dans cette direction, protégé tout le temps par une suite solide de tests unitaires. La présomption derrière TDD est que vous ne pouvez pas vraiment dire quelle conception vous servira le mieux tant que vous n'aurez pas les bras enfoncés dans le code. Au fur et à mesure que vous découvrez ce qui fonctionne réellement et ce qui ne fonctionne pas, vous êtes dans la meilleure position possible pour appliquer ces idées, tant qu'elles sont encore fraîches dans votre esprit. Et toute cette activité est protégée par vos suites de tests unitaires automatisés.

Vous pouvez commencer avec une bonne quantité de conception à l'avant, bien qu'il soit plus courant de commencer avec assez conception de code simple; quelques croquis UML sur tableau blanc suffisent souvent dans le monde de la programmation extrême. Mais la quantité de conception avec laquelle vous commencez a moins d'importance, avec TDD, que la mesure dans laquelle vous permettez à cette conception de diverger de son point de départ au fur et à mesure. Vous n'apporterez peut-être pas de changements architecturaux radicaux, mais vous pourriez refactoriser le modèle objet dans une large mesure, si cela semble être la chose la plus sage à faire. Certains magasins ont plus de latitude politique que d'autres pour mettre en œuvre un vrai TDD.

Tester d'abord ou déboguer

Il est utile de comparer les efforts consacrés à l'écriture des tests au temps passé à déboguer. Le débogage implique souvent de parcourir de grandes quantités de code. Le travail de test en premier vous permet de vous concentrer sur un morceau de la taille d'une bouchée, dans lequel moins de choses peuvent mal tourner. Il est difficile pour les responsables de prédire combien de temps le débogage prendra réellement. Et dans un sens, tant d'efforts de débogage sont gaspillés. Le débogage implique un investissement en temps, un échafaudage et une infrastructure (points d'arrêt, surveillance des variables temporaires, instructions d'impression) qui sont tous essentiellement jetables. Une fois que vous avez trouvé et corrigé le bogue, toute cette analyse est essentiellement perdue. Et s'il n'est pas entièrement perdu pour vous, il est certainement perdu pour les autres programmeurs qui maintiennent ou étendent ce code. Avec le travail de test d'abord, les tests sont là pour que tout le monde puisse les utiliser, pour toujours. Si un bogue réapparaît d'une manière ou d'une autre, le même test qui l'a détecté une fois peut le détecter à nouveau. Si un bogue apparaît parce qu'il n'y a pas de test correspondant, vous pouvez écrire un test qui le capture à partir de là. De cette façon, de nombreux praticiens qui testent d'abord affirment que c'est la quintessence de travailler plus intelligemment au lieu de travailler plus dur.

Tester d'abord la technique et les outils

Il n’est pas toujours trivial d’écrire un test unitaire pour chaque aspect du comportement d’un système. Qu'en est-il des interfaces graphiques ? Qu’en est-il des EJB et des autres créatures dont la vie est gérée par des frameworks basés sur des conteneurs ? Qu’en est-il des bases de données et de la persistance en général ? Comment tester qu’une exception est correctement levée ? Comment tester les niveaux de performances ? Comment mesurez-vous la couverture des tests, la granularité des tests et la qualité des tests ? La communauté des tests répond à ces questions avec un ensemble d'outils et de techniques en constante évolution. Une immense ingéniosité continue à être déployée pour permettre de couvrir tous les aspects du comportement d'un système avec des tests unitaires. Par exemple, il est souvent judicieux de tester un composant d’un système indépendamment de ses collaborateurs et des ressources externes, en utilisant des objets contrefaits et fictifs. Sans ces simulations ou contrefaçons, vos tests unitaires pourraient ne pas être en mesure d'instancier l'objet testé. Ou dans le cas de ressources externes telles que des connexions réseau, des bases de données ou des interfaces graphiques, l'utilisation de la version réelle dans un test peut le ralentir considérablement, tandis que l'utilisation d'une version fausse ou fictive permet à tout de fonctionner rapidement en mémoire. Et même si certains aspects de la fonctionnalité peuvent toujours nécessiter test manuel, le pourcentage pour lequel cela est incontestablement vrai continue de diminuer.