Le mutation testing

J’ai récemment lu un article de l’oncle Bob Martin, il y expose sa découverte du mutation testing et semble très enthousiaste à ce sujet. J’ai donc décidé d’essayer un outil pour mieux comprendre cette démarche.

Le principe

Aujourd’hui, beaucoup de projets sont réalisés en appliquant le TDD. Développer en appliquant le test first permet d’être sûr que l’on écrit uniquement le code nécessaire pour rendre un test valide.

Cependant, certains reprochent à cette méthode de mettre en évidence la présence de bugs, et non de démontrer l’absence de bug : un test qui échoue montre qu’il y a une anomalie, mais une anomalie peut exister sans qu’il n’y ait de test pour le montrer.

L’idée du mutation testing est de créer des mutations sur le code testé. Un outil analyse le code couvert par les tests puis génère des mutants : Mes tests sont-ils toujours vrais si je modifie cette condition ? Et si je ne fais pas d’appel à cette fonction ? Un mutant peut avoir deux états : mort ou vivant .

Les mutations peuvent prendre diverses formes : la modification d’une limite conditionnelle (< devient <=), l’inversion d’une condition (== devient !=), la suppression d’un appel à une méthode, etc.

Un mutant mort montre qu’au moins un test échoue si l’on modifie le code, on peut donc en déduire que les tests protègent bien le code contre les régressions. Un mutant vivant montre que tous les tests passent malgré une modification du code. Le mutation testing peut ainsi révéler que le code est mal protégé contre les régressions, il peut s’agir d’un problème de design ou alors c’est la qualité des tests qui peut être remise en cause.

Exemple

Pour mon exemple, j’utilise VisualMutator qui s’intègre directement dans visual studio.

Cas initial

Ici, je teste de manière laxiste une simple méthode qui me dit si mon objet Sequence contient un seul élément. Voici une première solution :

mutationtesting1

mutationtesting2Mutations

Après une première session de mutation sur mon code on constate des faiblesses dans mes tests :

mutationtesting3

Le mutant LessThanOrEqual me montre que je peux modifier ma condition tout en gardant mes tests valides. Je le constate bien si j’applique cette modification (< 2 devient <= 2).

mutationtesting4

Je peux ici rejeter la faute à mon dernier test qui fournit une liste de trois objets. Une fois corrigé je peux relancer un test par mutation :

mutationtesting5

mutationtesting6

On constate bien cette fois que la mutation LessThanOrEqual n’est plus vivante. Mais cette fois ci le mutant NotEquality reste vivant, il me manque donc clairement un test.

mutationtesting7

Cette fois ci je constate que mes mutants LessThanOrEqual et NotEquality sont tous les deux tués par mes tests.

mutationtesting8

L’utilité

Cette approche est clairement faite pour tester la robustesse des tests plus que le code en lui même. Elle permet de mettre en évidence les limites de notre jeu de tests, et ainsi la présence de potentielles anomalies non identifiées. En d’autres termes : Est-ce que je peux faire confiance à mes tests ?

Je ne suis donc pas convaincu que le mutation testing apporte une grande plus-value si le TDD est appliqué avec rigueur. J’avoue ne pas avoir su produire de mutant vivant sur un premier exemple écrit de cette manière.

Cette approche est donc beaucoup plus intéressante pour la gestion de legacy. Avant d’y apporter des modifications, mieux vaut écrire des tests pour se protéger contre les régressions. N’importe quel développeur ayant réalisé cet exercice sait qu’il s’agit d’une tâche complexe et qu’il est parfois difficile d’identifier tous les cas gérés. Utiliser le mutation testing peut facilement mettre en évidence ces cas non identifiés.

L’inconvénient

Il faut tout de même avoir conscience que cette méthode se révèle extrêmement coûteuse comparée à de simples tests unitaires. Il faut considérer le temps passé à l’analyse du code, à la génération des mutants, ainsi qu’à l’exécution des tests pour chaque mutant, ce qui peut prendre plusieurs heures sur un projet conséquent.

De manière grossière, imaginons un projet de 200 classes avec en moyenne 5 mutants par classe et un jeu de tests complet qui est exécuté en 30 secondes. On obtient :

200 * 5 * 0.5 = 8h20 (500 minutes)

Les tests utilisant la mutation ne peuvent donc pas être joués de manière systématique comme le sont les TUs. Il est selon moi beaucoup plus intéressant de l’appliquer de manière ponctuelle sur des régions ciblées du code.

Merci à Nadège pour sa relecture.

Le mutation testing

A whole team approach

Il y a quelques mois maintenant, j’ai eu l’occasion d’assister, grâce à un user group local, à une présentation de Sebastien Lambla. Celle-ci était intitulée Your Agile is Dead. J’avoue que je ne savais pas trop de quoi allait pouvoir traiter une présentation avec un tel nom, il s’agissait en réalité d’un retour expérience sur un projet réalisé en mob programming.

J’étais déjà convaincu par les bénéfices pour un projet d’appliquer le pair programming ainsi que les autres méthodes portées par l’Extreme Programming. Cette présentation m’a montré que l’on peut aller encore plus loin, j’ai donc décidé d’essayer de l’appliquer à mon équipe.

Le mob programming c’est quoi ?

“Mob” est le terme anglais pour désigner la foule. On peut présenter le mob programming comme une sorte de pair programming++ qui implique l’ensemble de l’équipe pour la réalisation d’une seule tâche.

WoodyZuill

Le Mob a été initiée par Woody Zuill. Elle découle d’un besoin récurent sur la plupart des projets : se réunir pour aborder des sujets complexes impliquant l’ensemble de l’équipe. Woody a alors décidé de réaliser ces réunions en y introduisant la notion de Driver / Navigator issue du pair programming. Cette pratique leur a immédiatement paru extrêmement pertinente et est entrée dans leurs habitudes.

Bien que je ne sois pas allé aussi loin, il est tout à fait possible de réaliser des projets entiers en mob programming.

Introduire le mob programming dans une équipe

Pour pouvoir l’appliquer, il m’a tout d’abord fallu convaincre de l’utiliser. Ceux qui ont déjà essayé de proposer le pair programming à des gens avec une vision (trop ?) “classique” du développement doivent imaginer ma principale problématique.

Toute l’équipe sur une seule tâche !? Tu veux ruiner notre productivité !

Mon discours était complètement informel, je me contentais de faire connaître la méthode autour de moi, à mes collègues, à mes pilotes, à mes managers.

Heureusement, tout le monde ne s’est pas montré aussi sceptique (pour ne pas dire réfractaire), d’autres se sont montrés plus curieux. C’était le cas de mon équipe avec laquelle nous sommes en accord sur les bonnes pratiques et les méthodologies de travail. L’amélioration continue fait partie de nos obsessions.

C’est lors d’une rétrospective d’équipe que nous avons décidé d’expérimenter le mob programming. Nous constations tous que le pair programming nous apportait de réels bénéfices, j’ai alors “officiellement” lancé l’idée. Comme nous sommes parfaitement autonome d’un point de vue opérationnel, notre pilote s’est vu obligé d’accepter malgré ses réticences du moment. De plus, nous avions une tâche idéale que nous souhaitions cibler pour cette expérimentation.

Notre expérimentation du Mob

Pour pratiquer le mob programming, mieux vaut être bien installé. Vous avez tout d’abord besoin d’un espace pouvant accueillir votre équipe, et assez calme pour pouvoir échanger facilement. Pour cela, une salle de réunion est sans doute la bonne solution. Il faut ensuite prévoir un poste de travail avec le support visuel sur lequel travailler : une télévision, un projecteur, … Il est important que tous les membres de l’équipe voient correctement ce qui est en train de se passer. Nous avions également plusieurs souris et claviers afin de facilement se passer la main. Ajoutez éventuellement un ou plusieurs laptop pour des tâches annexes telles que des recherches internet, consulter une documentation ou la rédaction d’un email. Et enfin, prévoyez un support sur lequel vous pourrez facilement représenter vos idées pour faciliter les échanges, un tableau blanc et quelques post-it feront l’affaire !
IMAG0427

Concernant mon équipe, nous étions cinq personnes avec différents profils :

  • un fonctionnel
  • trois développeurs
  • un recetteur

Pour nous faire une idée précise de la puissance du mob programming, nous avons choisi une évolution métier complexe à réaliser sur notre projet. Nous avons lancé la séance une fois que nous étions sûr d’avoir tous les éléments nécessaires pour réaliser la tâche demandée, aucun autre travail de préparation n’avait été réalisé en amont.

La première partie de la séance a donc consisté en une analyse du problème. Nous avons étudié les documents à notre disposition, regardé le code existant, débattu, schématisé, noté les points importants, … Ceci jusqu’à ce que tout le monde ait compris le besoin auquel il fallait répondre, ainsi que les tâches à réaliser. Cette étape nous a également permis de voir émerger un Ubiquitous Language au sein de l’équipe.

Est ensuite venu la réalisation. L’équipe a travaillé en TDD et BDD, nous avons traité les tâches les unes après les autres de cette manière, sans hésiter à lancer des micro-refactorings sur le code existant quand l’occasion se présentait. Même lors des phases de développement, la présence du fonctionnel et du recetteur s’est révélée très profitable. Cela leurs a permis de découvrir le red-green-refactor, mais aussi de challenger nos choix techniques.

Mais quand vous n’arrivez pas à tomber d’accord sur une décision technique, comment faites vous ?

C’est une question qui m’est posée de façon récurrente quand je parle du mob programming. Très honnêtement, nous n’avons pas rencontré cette situation. Une solution utilisée par Sebastien Lambla consiste à coder chacun de son côté la solution que l’on souhaite défendre pendant un temps donné, puis de comparer les résultats. De cette manière, vous révélerez plus facilement les avantages et les contraintes de chaque solution. Il vous faudra peut-être faire des concessions et accepter que votre solution n’est pas celle retenue.

Constats et résultats

Notre équipe s’efforce de travailler avec des approches DDD et BDD, le mob programming nous a permis d’aller dans ce sens. Une des principales problématiques dans le développement logiciel est la communication entre les différents métiers qu’il implique. Parce qu’ils ne parlent pas tous ensemble, et surtout pas tous en même temps, cela mêne à des incompréhensions et des lenteurs. Avec le Mob, vous enfermez les three amigos dans une salle pour résoudre un problème. Les échanges autour du besoin se font en direct avec des gens dont l’attention est entièrement portée sur ce besoin, vous êtes sûr de vous comprendre rapidement.

Comme nous avons été cinq à étudier le besoin, nous avons pu confronter cinq interprétations différentes des documents à notre disposition. Cet exercice est très intéressant à mener car nous avons immédiatement constaté des nuances dans nos compréhensions du besoin. Tant que vous n’arrivez pas tous à être d’accord sur le travail à réaliser, c’est qu’il existe une incompréhension qu’il faut résoudre, si vous n’y arrivez pas, c’est sans doute qu’il vous manque des éléments. A l’issue de notre séance, nous étions sûr d’avoir produit une solution adaptée au besoin.

Un autre avantage du mob programming est qu’il permet d’augmenter le bus factor de l’équipe à son maximum. Il s’agit du nombre de personne au deçà duquel votre projet n’est plus viable. Comme la totalité de votre équipe a travaillé sur le même sujet, la totalité de votre équipe possède la connaissance métier et technique. Pour perdre cette connaissance il faudrait que vous perdiez l’ensemble de votre équipe.

Même lors des phases de développement, notre fonctionnel et notre recetteur ont su challenger notre travail, en grande partie grâce à l’Ubiquitous Language et aux tests, mais aussi parce que nous avons fait l’effort de commenter à l’oral tout ce que nous écrivions. L’un d’eux s’est par exemple étonné d’une dépendance que nous utilisions dans une méthode, ce qui a révélé un problème de design dans notre code. La présence de trois développeurs à également de gros impacts sur la qualité du code, le principe est le même que pour le pair programming. Le code produit lors des séances de Mob est donc de très bonne qualité.

England v Australia - IRB Rugby World Cup 2015 Pool A

Notre productivité a sans doute été l’aspect du mob programming qui nous a le plus surpris ! Les solutions émerges très rapidement et l’équipe avance continuellement, un peu comme un bulldozer écarte tout ce qui se trouve sur son passage. Les séances sont très dynamiques, les idées fuses, il faut être capable de rester concentrer pour ne pas perdre le fil des événements. Le rythme peu même être tellement soutenu qu’il en devient épuisant, il est préférable d’aménager des pauses régulières, nous avons donc adopté la technique du pomodoro.

Le mob programming a également permis de resserrer d’avantage les liens dans notre équipe. Tout le monde est impliqué, tout le monde est au même niveau, tout le monde apporte sa contribution à une tâche potentiellement complexe, ce qui génère un sentiment de satisfaction général. De plus, c’est l’équipe et non un individu qui porte la responsabilité de ce qui a été produit, ce qui rend tout le monde beaucoup plus serein sans s’être déresponsabilisé pour autant.

Le Mob permet également de comprendre les problématiques de chacun. Mes problématiques de développeur ne sont pas celles de mon recetteur ni celles de mon fonctionnel. Travailler tous ensemble permet de mieux se comprendre, aborder les problèmes sous différents points de vues et ainsi de mieux avancer vers notre objectif. Cela permet également à chacun de partager ses compétences, son expérience, et ainsi de tirer le niveau de l’équipe vers le haut.

Nous avons donc été convaincus par l’efficacité du mob programming, les résultats obtenus ont créé une réelle émulation et cette pratique est entrée dans nos habitudes de travail. Si une tâche ou un problème complexe doit être traité, alors nous regroupons l’ensemble de l’équipe pour y répondre. Nos pilotes ont également été convaincus par les résultats obtenus et nous laissent désormais l’appliquer sans aucune réticence.

Le Mob, oui mais…

Je me dois d’apporter certaines limites au mob programming, il faut que certaines conditions soient respectée pour tirer les bénéfices de cette méthode de travail.

Le principale objectif du Mob est de pouvoir produire un logiciel de qualité :

  • qui répond de manière adaptée au besoin
  • dont le code source est propre et robuste

Si la qualité n’est pas votre priorité absolue, alors travailler en mob programming n’est sans doute pas la solution la plus adaptée pour votre équipe. Pour rappel, c’est parce que nous constations les gains de qualités obtenus grâce au pair programming que nous avons décidé de tenter l’expérience.

La grande force de cette pratique réside dans la communication et la collaboration qui sont grandement facilitées. Il me semble donc évident que votre équipe doit être désireuse de travailler ensemble, l’imposer à quelqu’un est un non-sens, et peut même se révéler pénalisant pour l’équipe. Les membres de l’équipe doivent également être ouverts à de nouvelles pratiques. Compte tenu de l’aspect “atypique” du mob programming, il ne faut pas avoir peur de changer certaines de ses habitudes de travail, ni de travailler devant ses collègues. J’ai pu animer plusieurs talks autour de ce sujet, je constate que ceux qui ont le mieux reçu le Mob étaient globalement des profils avec une bonne expérience de l’agilité. Pour cela, je pense qu’il est préférable que le pair programming soit déjà bien mis en place au sein d’une équipe avant d’aller plus loin.

Oui mais moi je suis dans une équipe de douze personnes…

Il y a effectivement une taille idéale pour un équipe qui travaille en mob programming, je pense qu’elle est de cinq ou six personnes. Cependant, il doit être possible de l’appliquer sur des projets avec des équipes plus conséquentes. Si c’est votre cas, vous ne travaillez sans doute pas tous sur les mêmes problématiques, vous pouvez alors identifier les personnes à regrouper pour travailler ensemble. Cela soulève des questions d’organisation.

La façon dont l’équipe est installée est un facteur clé ! Si vous n’êtes pas au calme, alors vous aurez des difficultés à échanger, vous serez interrompus. Nous avons constaté que, parce que nous étions tous ensemble isolés dans une salle de réunion, nous avons beaucoup moins été dérangé. Enfin, si tout le monde ne voit pas bien ce qu’il se passe à l’écran et ne peut pas intervenir à n’importe quel moment, alors vous risquez de perdre des gens en cours de route.

Si on pousse à l’extrême

Mon article décrit les impacts du mob programming lorsqu’il est appliqué ponctuellement. En y réfléchissant, je me rend compte que ces impacts sur le fonctionnement de mon équipe seraient bien plus profond si nous travaillons uniquement de cette manière.

Quelle utilité à maintenir le daily standup ? Idem pour nos tests croisés (revue du code d’un autre développeur avant de l’envoyer à la recette) ? Finalement, notre kanban ne devient-il pas à une simple todo list ?

Les exemples de ce genre sont nombreux, et remettent même en questions certaines pratiques de l’agilité. Voilà pourquoi Sebastien Lambla déclarait “Your Agile is dead” !

Merci à mes « co-mobeurs » Ouarzy et Nadège pour leurs reviews.

A whole team approach

Le code, c’est mieux à deux

Parmi toutes les bonnes pratiques mises en place dans mon équipe actuelle, il y en a une que j’apprécie tout particulièrement : le pair programming. Nous l’utilisons pour plusieurs raisons :

  • traiter des tâches complexes
  • faciliter les montées en compétence métier et technique des différents membres
  • augmenter la qualité du code

Je suis réellement convaincu par les avantages et les bénéfices qu’une équipe peut tirer du pair programming. Cependant, je constate que cette méthode n’est pas toujours bien comprise. J’entends régulièrement le même discours : si cette méthode marche “sur le papier”, elle ne semble pas “applicable en entreprise”.

Qu’est-ce que c’est ?

Le pair programming est l’une des pratiques portées par l’Extreme Programming, son principe est simple : deux développeurs travaillent en binôme sur la même tâche. On observe deux rôles :

  • Le driver qui écrit le code.
  • Le navigator / observer, il aide le driver en lui suggérant des solutions et en vérifiant le code au fil de l’implémentation.

Bien entendu, ces rôles sont régulièrement échangés. La règle d’or lors de ces échanges est : “déplacez le clavier, pas la chaise”. C’est pourquoi il est important que l’écran soit placé entre les deux développeurs pour faciliter les interactions et les échanges.

Une impression de “gaspillage”

Une crainte récurrente chez toutes les personnes qui n’ont jamais travaillé en pair programming est la perte de productivité : “Il y en a un qui écrit pendant que l’autre regarde.”, sous entendu celui qui n’écrit pas est passif.

Faineant
En vérité, si vous comparez la charge consommée par une personne et la charge consommée par un binôme, en effet vous consommerez plus. Il a ainsi été déterminé que des développeurs consomment environ 15% de charge supplémentaire lorsqu’ils pratiquent le pair programming, pourtant les bénéfices dépassent ce surcoût.

Pour calculer ce surcoût vous allez vous baser la grande majorité du temps sur des chiffrages, des estimations (Avez-vous vraiment fait deux fois le travail pour comparer ?). Selon mon expérience, les chiffrages sont par définition faux : ils se font généralement avec une vision idéaliste. Ils partent du principe que le besoin métier sera parfaitement compris et que la dette technique sera nulle. Ces deux facteurs pourtant majeurs sont généralement mal voire pas du tout pris en compte.

Ensuite, je trouve que s’appuyer uniquement sur ce 15% révèle une vision du projet à très court terme. Je pense qu’une charge de travail ne peut pas uniquement prendre en compte le temps de réalisation, il faudrait y ajouter le temps passé à corriger les anomalies ainsi que le coût de la dette technique engendrée.

Pour moi, il est donc beaucoup plus intéressant de comparer la qualité du code produit avec ces deux méthodes.

Faire les choses, mais les faire bien

Il ne faut pas oublier que ce sont bien deux personnes qui travaillent sur la même tâche, même s’il n’écrit pas, le navigator n’est absolument pas passif. La réflexion est partagée, les échanges sont dynamiques. Le fait d’échanger permet d’explorer plus facilement tous les aspects d’un problème. Cela a pour effet de dégager une compréhension claire du besoin et une solution appropriée de manière beaucoup plus efficace, tout en éludant plus rapidement les incompréhensions et les fausses routes. De plus, le navigator n’ayant pas à se concentrer sur l’écriture du code, il peut plus facilement prendre du recul et apporter un regard critique sur l’implémentation.

Le pair programming permet donc de proposer des solutions plus rapidement qu’un développeur seul. Il permet également d’améliorer la qualité sous deux aspects.

Tout d’abord, une quantité d’anomalies moindre. Une grande force du pair programming réside dans la revue continue. Quand vous écrivez du code, malgré toute votre bonne volonté, il n’est pas rare que vous fassiez des erreurs. Si la probabilité qu’une erreur vous échappe existe, la probabilité qu’elle échappe également à votre binôme est beaucoup plus réduite, elle sera ainsi signalée et corrigée immédiatement.

La qualité se retrouve également dans le design du code. Travailler à deux permet encore une fois de confronter sa compréhension du problème. Une solution technique qui peut sembler évidente pour une personne ne le sera peut être pas pour son partenaire, parce qu’il ne la comprend pas, ou parce qu’elle ne lui semble pas être la plus adaptée. Le pair programming fait émerger un meilleur design dans le code en confrontant les opinions et les expériences des deux développeurs.

Binome

Un autre avantage que je trouve au pair programming, c’est qu’il impose une grande rigueur dans le vocabulaire que vous utilisez. Employer une mauvaise notion conduit souvent à une incompréhension entre les deux membres. Comme il est important d’éliminer ces incompréhensions, vous allez attacher de l’importance à utiliser le terme, la métaphore la plus juste possible. Une fois la notion claire, explicitez la dans le code (réutilisez le terme employé à l’oral), vous dégagerez ainsi une forme d’ubiquitous langage. Cela rend votre travail plus simple à comprendre, et permet également à des personnes sans bagage technique de lire votre code. Ainsi, il nous arrive parfois de faire du pair programming avec notre product owner, il est parfaitement capable de comprendre ce que nous écrivons et de nous corriger si nécessaire.

Le pair programming permet donc de réaliser des tâches de manière plus rapide qu’un développeur seul, tout en garantissant un nombre d’anomalies plus réduit. On observe des gains à court terme avec moins de corrections à apporter suite aux développements. Le code étant plus propre, il est plus facilement maintenable et évolutif. Les gains se font également sur le long terme grâce à une dette technique réduite.

Certains comportements à éviter

Comme pour n’importe quelle méthode, il faut faire attention à certains détails lors de la mise en pratique du pair programming. Ici, le facteur humain et la bonne communication sont les deux clés pour assurer l’efficience de votre travail.

Quand vous travaillez en binôme, il faut faire attention au comportement de chacun. Si l’un des deux développeurs est passif, qu’il parle peu ou qu’il n’ose pas proposer des solutions, alors vous avez un problème. Le pair programming peut parfois générer de la défiance vis à vis de son partenaire. Il ne faut pas oublier de rester humble et de faire attention à son comportement.

Concrètement, travailler de cette manière va donner à votre binôme une vision claire de vos compétences, de votre façon de travailler. Si vous craignez de les exposer, c’est que vous avez probablement des choses à améliorer. Voyez le pair programming comme une excellente occasion pour progresser et vous ouvrir à de nouvelles pratiques, la séance n’en sera que plus profitable pour vous.

Nous avons tous une expérience différente, il faut en être conscient et veiller à ne pas s’enfermer dans une réflexion du type “il est trop fort, il est trop nul pour moi”. Tout d’abord parce que ce raisonnement ne se concentre que sur des compétences techniques purs. Comme je l’évoquais plus haut, la connaissance métier est essentielle pour répondre à un besoin. Si une personne est effectivement plus expérimentée, cela ne lui garanti pas d’avoir la meilleure compréhension du besoin, ni la meilleure solution technique pour y répondre. Personnellement, je travaille avec deux autres développeurs beaucoup plus expérimentés que moi, pour autant, je n’ai pas peur de proposer des solutions. Certaines sont retenues, d’autres non. L’essentiel est que toutes les propositions alimentent notre réflexion : Est-ce une bonne solution ? Pourquoi ?

ego-knowledge-ygoel-com_
Un autre comportement à éviter, et qu’il m’est déjà arrivé de rencontrer (à ma grande surprise ce jour là), est un esprit de compétition. Concrètement, mon collègue refusait ma solution qu’il jugeait pourtant comme étant bonne parce qu’elle ne venait pas de lui, nous avons perdu beaucoup de temps dans des débats qui étaient inutiles. Gardez à l’esprit que le pair programming vous fait travailler ensemble, pas l’un contre l’autre. Si un développeur se sent obligé de revendiquer le travail réalisé, alors il ne semble clairement pas fait pour travailler en binôme, ni en équipe. Lui imposer le pair programming ne sera absolument pas bénéfique pour lui, son partenaire ainsi que pour le projet.

Gagner en confiance

Quand je développe seul, il m’arrive régulièrement de me demander après coup “Est-ce que j’ai bien géré ce cas ?”, la grande majorité du temps oui, mais je me sens obligé de vérifier. Avec le temps, je constate que je suis beaucoup plus serein après une séance de pair programming. Mon argument est toujours le même : si quelque chose m’a échappé, il n’a probablement pas échappé à mon partenaire. Si effectivement je réalise que nous avons oublié un cas, j’en serai certain car je n’aurai aucun souvenir d’avoir échangé autour de son implémentation (Quel test écrire ? Quel design adopter ? …).

Avoir une telle confiance en ses collègues ne se fait pas du jour au lendemain, mais au fil des séances. Parce que vous apprenez comment l’autre réfléchit et procède, et parce que vous constatez qu’il est capable de déceler vous erreurs. Une fois cette confiance installée, vous redécouvrez le vrai sens du mot “équipe” : des gens avec qui vous collaborez et sur qui vous pouvez compter.

Merci à mes reviewers Ouarzy et Nadège.

Le code, c’est mieux à deux

Les tests, contre vents et marées

Connaissez vous la bernache nonnette ?

Bernache
Il s’agit d’une espèce d’oie vivant principalement dans les îles arctiques. La bernache est exclusivement végétarienne, et ce dès sa naissance. Le problème, c’est qu’elle a pour habitude de nicher au sommet d’une falaise pour se protéger elle et ses œufs des prédateurs terrestres. Les oisillons ne sachant pas voler avant l’âge de 40-45 jours, il faut que ceux-ci puissent descendre de la falaise pour se nourrir.

Un grand saut

Pour ce faire, les parents se placent en bas la falaise et appellent leurs petits jusqu’à ce que ceux-ci, guidés par leur instinct, se décident à sauter dans le vide. La chute est pour le moins impressionnante, parfois sur plus d’une centaine de mètres avec un éboulis en contre bas. Malheureusement tous n’y survivent pas, certains font une mauvaise réception, d’autres continuent à dévaler la pente avant de s’arrêter. Les rescapés sont quant a eux sonnés pendant quelques temps, ce qui relève déjà de l’exploit compte tenu de l’impact auquel ils viennent de résister.

SautVide

Replaçons nous maintenant dans un contexte de développement logiciel. Vous êtes un développeur travaillant sur un projet professionnel. Votre manager / client (rayez la mention inutile) a une influence préjudiciable, en effet il vous pousse à produire rapidement en négligeant les bonnes pratiques. Qu’allez vous faire ?

Avancer sur un projet sans bonnes pratiques, c’est comme sauter dans le vide quand on ne sait pas voler : vous ne maîtriserez rien, vous vous contenterez de subir (je reviens sur ce point plus bas). Avec de la chance vous en viendrez tout de même à bout, mais dans tous les cas cela se fera dans la douleur.

Une progression maîtrisée

Je suis un développeur passionné, qui éprouve un réel plaisir à produire des applications de qualité. J’ai donc énormément de mal à m’imaginer être obligé de sacrifier mes bonnes pratiques au nom d’une soi-disant productivité.

Il y en a une qui, selon moi, doit être utilisée pour n’importe quel projet : les tests unitaires. L’application du TDD associé à un processus d’intégration continue me semble indispensable. Le but est simple : les développeurs ont un feedback le plus rapide possible à chaque opération menée sur le code source. Cela permet de maîtriser le périmètre de l’application au fil de son développement, et ainsi de le faire évoluer sans dégrader l’existant.

Pour faire un parallèle avec notre bernache, utiliser les tests unitaires revient à choisir la cage d’escalier plutôt que la chute libre sans parachute. Vous descendez la falaise palier par palier, avec des états intermédiaires stables. Cette solution est beaucoup moins douloureuse pour tout le monde..

Oui, vous étiez déjà convaincus, mais pourtant vous ne le faites pas. Je vous vois venir…

Mes managers ne veulent pas des tests

On ne va pas se mentir, les miens n’étaient pas convaincus non plus, ils ont fini par changer d’avis. Mais finalement, pourquoi ?

“Parce qu’écrire les tests ça fait perdre de temps.”

On a tous déjà entendu quelqu’un tenir un discours de ce genre. Il peut vous sembler absurde, mais il a réellement du sens pour celui qui le prononce. En fait ces personnes n’ont pas toujours conscience des problématiques d’une équipe de développement. Un logiciel se résume pour eux à des fonctionnalités qu’il faut implémenter en évitant au maximum les anomalies. Ils ne voient que la partie émergée de l’iceberg, vouloir travailler avec des tests peut donc ressembler à un “caprice de développeur”.

ValeursLogiciel

Contrairement à ces personnes, si vous souhaitez travailler avec des tests, c’est que vous avez compris les véritables enjeux de telles pratiques. Vous avez conscience qu’une bonne architecture vous permet d’avancer de manière efficace, en conservant une souplesse de travail et une facilité à maintenir l’existant. A l’inverse, plus le code legacy s’installe dans votre projet, plus votre travail va devenir complexe, et par conséquent vos taches vont devenir de plus en plus longues.

En fait, si vous souhaitez faire accepter les tests à des personnes qui n’en veulent pas, il faut leurs faire comprendre l’influence du code legacy.

Le code legacy

Pour faire court, le code legacy est du code qui n’est pas testé, que vous ne comprenez pas et/ou dont vous ne maîtrisez pas les effets de bord. Un de mes collègues le définit aussi comme le code que l’on a peur de modifier.

PierresEquilibre

On peut voir mes pierres comme un legacy, l’équilibre de la structure est des plus précaire. A la moindre modification que vous allez tenter, vous ajouterez un peu plus de legacy à celui que vous subissez déjà, tout en risquant de voir le l’ensemble s’effondrer. Plus grave encore, vous ne serez pas capable de mesurer les effets de bord produits par vos modifications. Vos développements seront donc de plus en plus longs, de plus en plus difficiles et source de plus d’anomalies. Vous subissez véritablement votre projet, vous êtes pris dans un cercle vicieux.

TestsVicieux

C’est pourquoi l’argument “les tests ça coûte du temps” n’est pas recevable. Oui, pris de manière isolée, un test a un coût. Mais nous les écrivons pour valider des comportements et protéger notre code contre les régressions. Si vous écrivez des tests, vous vous affranchirez donc du code legacy puisque vous maîtriserez ce qui est codé. Cela vous donne également la possibilité de faire du refactoring.

Le refactoring

Encore un gros mot qui fait peur à votre manager. Pourtant le refactoring est essentiel dans la vie d’un projet, surtout si vous souhaitez tendre vers une meilleure architecture.

Le refactoring, c’est comme le code du boy scout. Si je vais camper quelque part, alors je m’assure de laisser cet endroit propre en le quittant. Je ramasse donc mes déchets, mais aussi ceux des gens qui sont passés avant moi. Dans une application, le but est donc de toujours rendre le code plus lisible, l’architecture plus souple, le modèle plus explicite… Le comportement du code que je vais modifier étant protégé par les tests, je peux faire mes modifications sans risque de régression.

Tous les refactoring n’impliquent pas des modifications majeures, en fait, c’est même le contraire. Il peut s’agir d’actions très simples comme renommer une propriété, supprimer une dépendance inutile, etc. On parle généralement de micro-refactorings, ils prennent peu de temps s’ils sont menés tout au long du projet, de manière complètement informelle, dés qu’une opportunité se présente.

Un refactoring majeur implique généralement une évolution d’un concept clé dans l’architecture. Elle se fait soit pour répondre à une évolution du métier, soit pour éliminer un problème de conception. Ils ne sont donc pas systématiques.

L’application des tests est donc vertueux pour un projet. Il vous permet de ne pas souffrir du code legacy et vous donne également les moyens d’améliorer votre architecture. Vous gagnez en temps et en qualité !

TestsVertueux

Mon client ne veut pas payer pour des tests

Ce n’est pas bien grave puisque votre client a un besoin. Le logiciel qu’il vous demande doit y répondre, bien y répondre même puisque qu’il a une valeur ajoutée pour votre client. Finalement, la façon dont vous travaillez ne le concerne pas, alors pourquoi se contraindre avec ce qu’il veut ? S’il est soucieux de la qualité de son produit, vous pouvez valoriser vos pratiques. Dans le cas contraire, vous pouvez juste “omettre” d’en parler. D’autant plus que nous avons vu que les tests n’augmentent pas les coûts.

Si vous souhaitez tout de même lui faire accepter, alors faites lui voir le développement logiciel comme de l’artisanat : du Software Craftsmanship. Ce qui a du sens, on ne produit pas une application comme l’on produit une voiture sur une chaîne de production. Chaque projet est différent, on ne sait donc pas automatiser les développements, le facteur humain est par conséquent très important. Votre client paye pour une expertise qu’il n’a pas lui même. Il me semble donc aberrant qu’il puisse vous dicter la façon dont vous allez travailler.

On peut s’imaginer faire appel à un plombier pour réparer une fuite d’eau dans votre cuisine. Vous avez deux options :

  • Pour payer moins cher, n’amenez pas vos outils, je vous fournirai les miens. Résultat, votre pauvre plombier se retrouve avec une clé de 12 et un vieux rouleau d’adhésif pour faire sa réparation. Étant mal équipé, il répare tant bien que mal votre fuite, ce qui lui demande plus de temps qu’à son habitude, et pour un résultat des plus douteux. Bref, la réparation ne tiendra pas et vous devrez faire refaire le travail deux semaines plus tard, ce qui va vous coûter cher.
  • Vous lui faites confiance, après tout c’est un professionnel. Étant parfaitement équipé, le travail est rapidement réalisé et de qualité. Finalement vous n’entendrez plus parler de votre fuite d’eau. Vous avez payé la qualité du service.

N’importe qu’elle personne est capable de comprendre ceci, il suffit juste de le lui faire entendre.

Accepter des exigences de ce genre revient à se tirer une balle dans le pied. Votre travail ne sera pas de la qualité espérée, et votre client ira probablement voir chez la concurrence quand il aura à nouveau besoin d’une prestation informatique. A l’inverse, prôner la qualité vous fera peut-être perdre quelques clients proches de leur argent, mais vous assurera une image de marque grâce à une très grande majorité de vos clients satisfaits et fidélisés.

Pour conclure

J’espère avoir donné quelques éléments pour expliquer l’intérêt des tests unitaires, aussi bien pour vous que pour votre entreprise et vos clients. Si malgré tout vous n’arrivez pas à convaincre, vous êtes toujours libre de passer outre, les résultats finiront par parler d’eux mêmes.

J’ai fait partie de l’équipe qui a introduit pour la première fois les tests unitaires dans mon entreprise. Ce sont les développeurs qui ont pris et porté cette décision. Finalement, le projet s’est révélé être un véritable succès, le suivant également. Les mentalités ont fini par changer, à tel point que tous les projets sont maintenant vendus en affichant clairement ces pratiques.

Enfin, Martin Fowler explique beaucoup mieux que moi tous ces enjeux, notamment au travers de l’intégration continue.

A vous de jouer !

Merci à mes reviewers Ouarzy et Nadège.

Les tests, contre vents et marées

Enseigner le TDD

Cela fait maintenant un an que je travaille comme prestataire pour un grand groupe. Ma mission y est des plus critique. Elle consiste à développer et maintenir l’ensemble des projets utilisés pour la programmation d’objets connectés nécessaires à l’activité de l’entreprise.

Mon équipe est constituée de plusieurs profils : des développeurs, un product owner ainsi qu’un recetteur. Pour répondre à un fort besoin de qualité, les tests, et notamment les tests unitaires, constituent une composante majeure de notre travail. Les développements se font donc systématiquement avec une approche TDD / BDD. Ces pratiques ont été mises en place par l’équipe qui est complètement libre d’un point de vue opérationnel.

Au cours de cette mission, j’ai à deux reprises animé des ateliers d’initiation au TDD. La première était destinée à notre recetteur qui souhaitait participer au développement de ses outils de test. La seconde concernait un nouveau développeur ayant déjà reçu une courte formation, mais qu’il n’a jamais pu (su ?) appliquer par la suite. Ces deux profils étant très différents, les problématiques rencontrées n’ont donc pas été les mêmes, ce qui a rendu l’exercice extrêmement intéressant.

Certains pré-requis

Après plus d’un an passé à l’appliquer sur tous mes projets, le TDD est devenu une habitude pour moi, l’appliquer me semblait simple. Dans un premier temps, j’ai donc pensé qu’il serait simple de l’inculquer. Que nenni !

Lors de mon premier atelier avec notre recetteur, la principale problématique que j’ai rencontré était liée à des questions de design. En effet, celui-ci n’avait jamais reçu de véritable formation, ses connaissances se résumant à ce qu’il avait appris sur le tas. Comme tout débutant (moi le premier à mes débuts), son code souffrait d’un véritable manque d’organisation, il était fortement couplé. Selon mon expérience, le premier pré-requis consiste donc en un minimum de compétences en terme de conception (le sujet porte à débat), ceci pour que le développeur puisse isoler les différentes responsabilités de son code.

Toujours sur des problématiques de design, nous avons ensuite travaillé sur la façon dont les éléments de notre code interagissent entre eux. Je lui ai donc expliqué l’utilisation de l’injection de dépendance. Cela consiste à expliciter ce que va utiliser le code (une fonction, une classe) en injectant les dépendances nécessaires pour son fonctionnement. De plus, pour réduire le couplage, les dépendances sont abstraites grâce à des interfaces (ou des classes de bases). Cette pratique répond au L, au I et au D de SOLID.

code1
Dans mon exemple, j’explicite que pour calculer un prix, ma classe CashRegister a besoin d’un objet qui respecte le contrat d’interface IBasket. L’usage d’une interface réduisant le couplage, il est facile d’injecter des comportements spécifiques lors de mes tests sans dépendre d’une fonctionnalité annexe.

Une fois que le développeur maîtrise ces concepts, il lui devient possible d’apprendre à écrire des tests unitaires. Dans le cas contraire, il aura du mal à percevoir le fonctionnement du TDD car il aura beaucoup trop de problématiques annexes à gérer.

Donner du sens aux tests : une approche métier

Pour l’apprentissage du TDD en lui même, les difficultés ont été plus grandes pour notre nouveau développeur. En effet, ayant plus d’expérience, il lui a été plus difficile de sortir d’une de ses habitudes : essayer d’apporter une réponse technique à un besoin métier. Que l’on ne se méprenne pas, je parle ici d’un problème de méthodologie.

Très concrètement, lors d’un atelier, j’exprime successivement des besoins métiers en aidant le développeur à appliquer le TDD pour les implémenter. Les besoins évoluent de sorte que le code nécessite un refactoring au cours de l’exercice. A ce moment survient un problème récurent : “Comment vais je coder ça ?” Le développeur n’est pas capable d’écrire son test si il ne sait comment va être écrit son code. C’est là que j’explique l’importance des concepts métiers dans les tests.

Un avantage des tests unitaires est qu’ils documentent le code. Un test spécifie le comportement qu’il valide. Mais ce test n’a finalement pas de réel valeur si il n’explicite pas l’utilité de ce comportement. Par exemple : WhenComputePriceThenReturnValue n’est pas un intitulé très clair. Je calcule un prix, très bien, mais le prix de quoi ? Comment est-il calculé ?

Le même test avec pour nom WhenComputePriceThenReturnSumOfArticlesPriceOfBasket exprime mieux la règle métier testée. Personnellement j’utilise le formalisme proposé par Sandro Mancuso : le nom de la classe et de la méthode de test se lisent comme une phrase.

code2

Si votre élève commence à se questionner sur la façon dont il peut implémenter, coupez court et recentrez son attention sur la rédaction d’un test explicite.

Maintenant que le test est écrit, le développeur peut réfléchir à comment implémenter. Le test décrit les éléments nécessaires ainsi que leurs interactions, c’est donc la façon dont le code doit être écrit. Au cours de l’exercice, il est donc important de faire attention aux termes que vous allez employer quand vous formulerez un nouveau besoin.

Faire passer un cap

Bien entendu, la maîtrise de cette méthodologie ne s’acquière pas en un simple atelier, seule la pratique le permet. Généralement, il arrive un moment où apprendre le TDD peut devenir décourageant pour le développeur. Parce que cela change ses vieilles habitudes. Parce que c’est une façon de réfléchir qui est fatigante, qui n’est pas encore naturelle, ce qui la rend difficile. Il est donc important d’accompagner le développeur jusqu’à ce que celui-ci soit familier avec le TDD. L’essentiel est de le suivre et de rester disponible pour l’aider quand il en ressent le besoin. Pour cela, des revues de son code (et ses tests) ainsi que des séances de pair programming peuvent être des bons moyens pour l’aider à progresser.

Lors de travail en pair programming avec mon équipe, il nous arrive d’appliquer le ping-pong programming. Nous procédons de la manière suivante : un développeur écrit un test, le second le fait passer puis écrit le test suivant, et ainsi de suite. Cela nous permet de nous challenger et ainsi d’améliorer le niveau de chacun. C’est sans doute la meilleure technique que je peux conseiller pour un travail dans la durée.

Conclusion

L’apprentissage du TDD est un travail quotidien qui nécessite une implication du développeur et un accompagnement pour débuter.

Merci à mes reviewers Ouarzy et Nadège.

Enseigner le TDD