Dans un programme que l'on voudrait écrire complètement conduits par les tests, quel est le premier test à écrire ?

Il est rare de commencer l'écriture d'un programme complet à partir de rien. Rare par rapport au temps passé à développer, modifier ou maintenir de l'existant. Il est un peu moins rare de démarrer des prototypes, vite fait, pour vérifier un concept, s'entraîner ou résoudre rapidement un problème. Ce sont malheureusement ces prototypes qui sont souvent les embryons d'une future application. Et le temps de se retourner et de mettre en place les outils de qualité nécessaires, l'application à déjà grandi jusqu'au point où l'on est déçu de certaines fondations, non testées.

Au tout début

Mais alors, lorsque l'on part de zéro, quel est le premier test à faire, en suivant les principes du TDD ?

Lors de mes derniers essais sur le sujet, sur une application desktop en C++ d'un côté et sur une application Web en node.js de l'autre, mon premier test était l'existence du programme.

Pour le programme en C++, un simple script cherchait la présence d'un exécutable et échouait sinon. La compilation via un simple batch d'un main() résolvait le test, puis le tout évoluait vers un système de build plus complexe alterné avec des tests de fonctionnalités. Pour le programme en Node.js, le premier test était similaire et tentait d'accéder à la page web. Ce test passait tout d'abord avec une page statique puis évoluait avec la mise en place de l'application avec un framework plus complexe.

Cela donne un démarrage plus lent que lorsqu'on se lance directement dans les fonctionnalités d'une application et peut être frustrant. Je trouve au contraire que le plaisir de se retrouver rapidement avec quelque chose de solide que l'on peut faire évoluer en confiance est agréable. Et évite la frustration d'une complexité galopante. C'est pour cela qu'il faut bien séparer le prototype rapidement écrit qui permet de prouver son idée, de bricoler, d'une application solide. Il faut pour cela trouver le courage de reprendre à zéro et faire les choses dans l'ordre, comme le préconise la méthode Mikado.

Avant le début

Récemment, je me faisais cependant la réflexion que ces premiers tests n'était pas les bons. Que j'allais trop vite, que je grillais des étapes. Je me faisais cette réflexion en réfléchissant aux problèmes que j'avais eu en transférant l'un de mes projets d'une machine à une autre. Et en constatant le nombre de problèmes remontés de manière générale lorsqu'un nouveau contributeur arrive sur un projet. Pas plus tard qu'il y a deux semaines, je voulais soumettre un patch assez simple sur un projet partagé sur github. Un projet de documentation. Malheureusement, l'incapacité à comprendre comment installer un environnement sur ma machine me permettant de générer la documentation en pdf pour vérifier ma modification m'a découragé au bout d'une heure. Une heure pour une contribution de trois lignes, ça ne vaut pas la peine.

Si je reviens à mon programme en C++. Lorsque je construis pour la première fois le main(), qu'est-ce qui me permet de le faire ? J'ai probablement les outils sur ma machine car ils ont servis au prototype. Mais rien ne garanti la présence de ces outils sur une autre machine de développement. On pourrait alors commencer la documentation de l'installation de l'environnement de travail. Mais surtout pas une documentation sur un site séparé de la distribution des sources. Et encore mieux qu'une documentation textuelle longue et complexe à maintenir, cette documentation peut prendre place dans une série de tests : est-ce que les éléments de mon build system sont là et accessibles dans la bonne version ?

Dans le cas contraire, échouer avec un message explicite sera plus utile et servira de documentation. Plutôt que laisser les appels aux outils de construction échouer émettre des messages cryptiques. L'échec du test peut même proposer d'embrayer directement sur une procédure d'installation. Avec une option pour répondre oui par défaut, on obtient un environnement de travail bootstrapable, qui permet d'embarquer des collaborateurs plus facilement.

Pour la version web, le test est similaire. Est-ce que npm est là, est-ce que curl est disponible ? Si non, l'échec est immédiat avec une explication des pré-requis.

C'est tellement plus agréable qu'un 'program not found'.

Une note tout de même : afin d'être valides dans du TDD, ces tests doivent d'abord échouer. Ce qui oblige à revenir dans un état de machine sans outils installés. C'est le moment de lancer une machine virtuelle.

Dès le début en continu

Une autre considération est que ces tests initiaux doivent immédiatement être mis sur un système de build en continue. C'est vrai, on ne build pas grand chose à ce moment-là. Mais il y a tellement de moments, surtout lors d'un début fourmillant de construction d'un nouveau programme où les tests peuvent sauter, que délayer cette étape serait regrettable. Très rapidement derrière, il faut aussi se poser des questions sur les standards de qualité que l'on veut utiliser. Chaque outil d'analyse, statique, de métriques, de vérifications de règles,... doit être mis en place au début. Toute mise en place tardive fera qu'une partie déjà écrite sera en défaut. Sans parfois avoir l'envie ni la possibilité de revenir dessus.

Les tests suivant la construction d'un exécutable sont donc selon moi les tests de qualité : est-ce que le programme sort sans défaut statique, est-ce que le main respecte les standards d'écriture choisis,... Puis en continuant dans cette logique : à la première allocation mémoire, valgrind/memcheck (ou un équivalent) entre en scène et teste que la mémoire est saine (attention, toujours en TDD, le test doit échouer une première fois).

Prendre la qualité comme un prérequis

Il est facile de programmer et d'obtenir des résultats gratifiants rapidement. Bricoler des prototypes, c'est amusant. Au fur et à mesure du développement d'une application de ce type, tout le monde fait l'expérience de la complexité grandissante, jusqu'à un moment se retrouver avec une complexité ingérable. Cette complexité est alors au mieux gérée, c'est-à-dire gardé dans certaines limites avec plus ou moins de succès et toujours beaucoup d'efforts et au pire ignorée, continuant à empiler les couches jusqu'à l'explosion.

Cette complexité n'est pas gratifiante, n'est pas amusante. Et produit quelque chose qui n'a pas la valeur visée.

Prendre le parti de la qualité dès le début, c'est avancer plus doucement, construire brique par brique un édifice qui va tenir dans la tempête. Et construire des choses solides avec une valeur visée, c'est gratifiant.