Bienvenue sur Développement Agile

Référence sur le développement logiciel Agile. Nous traitons de conception, de programmation, de pratiques de génie logiciel, d'essais et d'autres sujets connexes.

Leçon de vie

Publié par Jean-Francois Gilbert le lundi 9 mars 2015 à 00:00

Il y a quelques temps, j'ai commencé à travailler sur une preuve de concept relativement complexe. Au départ, je n'avais qu’une vague idée des outils et de l’architecture que j’allais utiliser. Il y avait tellement d'inconnus que je ne savais pas trop par où commencer. 

J'ai donc avancé le projet un peu à tâtons. Je m’efforçais de répondre aux exigences fonctionnelles complexes tout en explorant une plateforme que je connaissais peu. Bref, j'en avais un peu par-dessus la tête. Pour sauver du temps et parce que je croyais tout jeter à la poubelle de toute façon, j'ai commencé à coder sans écrire de tests unitaires. Je vois d’ici vos hochements de tête désapprobateurs. Mais je croyais que les chances que mon code puisse être réutilisé plus tard étaient tellement minces que ça me ralentirait d’écrire des tests tout de suite. Oh, mais quelle erreur !

Au début, je dois avouer que ça allait plutôt bien. J'essayais des choses, je changeais d'idée, j’effaçais le code, j’essayais autre chose. Pas de temps à perdre, je produisais du code à la vitesse grand V ! Étant habitué à programmer presqu'exclusivement en mode TDD, je ressentais quand même un certain malaise. Je n'avais pas mon filet de sûreté habituel et je n’étais pas particulièrement fier du design de mes classes. Mais bon, mon prototype fonctionnait, non ?

A un certain moment j’ai réalisé que globalement, la piste que je suivais depuis plusieurs jours était la bonne. Il y avait bien quelques inconnus ici et là, mais je commençais à avoir entre les mains quelque chose d’intéressant. Or, puisque j'avais codé un peu en cow-boy, le design n'était pas génial et ma solution commençait à être sens dessus dessous. J'ai donc commencé à faire du réusinage pour être capable de mieux me retrouver. C'est à ce moment que des bugs ont commencé à faire surface. Je me suis mis à perdre un temps fou. Les modifications étaient suivies par des séances de débogage dans Visual Studio et je maudissais les dieux de la programmation, mais surtout moi-même. Au lieu d'avancer, je faisais des pas en arrière à chaque fois que je modifiais du code.

Je n'ai eu d'autre choix que de recommencer du début. Mais cette fois, j'ai bâti mon code comme je suis habitué de le faire : en TDD. J’ai quelque fois eu le goût de prendre le code du prototype et le copier dans mes nouvelles classes, mais j'ai presque toujours résisté à la tentation. Bien sûr, j'ai gardé certains concepts mais le fait de recommencer du début a fait émerger un design différent et surtout meilleur.

En bref, soyez plus brillants que moi. Prenez le temps de bien faire les choses dès le début et en bout de ligne vous gagnerez du temps. Je crois qu'on peut se permettre d'expérimenter de nouveaux outils sans faire de tests unitaires exhaustifs. Mais il faut savoir identifier le moment où on a une assez bonne idée de la solution et reprendre les bonnes pratiques de développement.

En terminant, voici quelques billets qui pourraient vous intéresser concernant le TDD et les tests unitaires.

Jusqu'à quel point dois-je tester mon code ?

TDD : comment partir du bon pied ?

Comment rendre vos tests plus propres grâce aux builders ?

 

Avoiding Layers of Misdirection

Publié par Jean-Nicolas Viens le jeudi 29 mai 2014 à 09:53

Have mocks led you to add frustrating layers of indirection? You are not alone. This is an argument part of the ongoing debate with DHH (David Heinemeier Hansson), Martin Fowler and Kent Beck.

All three of them seem to be in agreement on one thing: mocks should be avoided. This is what I think brings you from layers of indirection to layers of misdirection.

Skippable summary

You may skip this section, even if you are not familiar with the topic. Refer to these links to continue the discussion!

Here are the main parts (I think), in order :

  • Is TDD Dead? David's blog post following his railsconf keynote. The subsequent articles are interesting as well.
  • Their firstsecond and third hangout sessions.
  • Google, twitter

These people have a lot more experience than I do, but I still don't see mocks the same way they do. Hopefully, this will make some sense. As for everything pertaining to this debate : what works for me might not work for you. But, it doesn't mean you should not try it.

This is obviously a vast debate with various angles of approach. If you want test isolation with proper mocking, Gary Bernhardt nailed it in my opinion. Read his post. If the test speed argument is one that bothers you, I suggest you read Corey Haines' article. If you are still unsure that integrations tests can harm you when not done properly, I could not explain it better than J. B. Rainsberger in this video.

What if mocks had nothing to do with it

The angle I want to tackle is one that has confused me a lot over the years. Sorry, there will be no definite answer at the end of this article, just a suggestion for a further conversation. I hope you chip in!

Over the years, I have seen people hate TDD because they had to do mocks. Doing mocks forced them to create new classes. Many of them got confused in their own design after three or four of these classes.

This phenomenon is also seen when you read someone else's code. If it's all in one place, you can read it in one go. Adding layers of indirection makes it harder to follow. DHH puts it this way :

[If I can look at something that fits within 10 lines of clean code], to me that's endlessly easier to understand than sixty lines that has three additional layers of indirection that I have to comprehend first.

I agree with this statement, up until he says "that I have to comprehend first". You don't. The name of the class or method you are calling should be enough to tell you what is happening there. You know the what, not the how. To me this is key to making complex business models work. I never add layers of indirection, I add layers of I-don't-care-ness.

But this is where I get confused. This makes sense to me, but most people seem to disagree. Everyone wants to understand every single layer first, but that is just impossible to do when you live in a world of complex business rules (as opposed to simple CRUD operations).

Maybe this is just a naming problem? Of course, if a method is poorly named, this will become a layer of misdirection. But I think there is more to it.

What if you just made it hard on yourself

Just let go.

Adding a layer, or simply a private method, allows me to understand a small portion of the algorithm faster than anything else. When you write a complex algorithm without tests, what do you do? Write a part of it, refresh, does it work? Yes, keep going. You iterate. Test the algorithm bit by bit. Adding layers allows you to do just that, in a TDD flow that makes sure you get every part of the algorithm right.

Here is a simple example. What does this do?


receipt = new Receipt(employeeNumber);
for(int i = 0; i < items.size; i++) {
    receipt.registeredItems.add(items[i]);
}
return receipt;

You can most likely figure it out, but this version should be faster to understand :


receipt = receiptFactory.create(employeeNumber);
receipt.registerItems(items);

What happened? Did I really remove code? Not at all. In fact, I would have added code. The number of lines of code pertaining to the business logic would be the same, but code to make the compiler happy would be added. That is class declaration, brackets, etc. It would even add one more file. 

Is it still worth it? Hell yeah! I've now stopped caring about two things: how to create a receipt and how to add items to it. Do you need to queue the new receipt somewhere? Don't know don't care. Do you need to add something between each items? Don't know don't care. Our brain is very limited, asking less of it each time is good. You get more lines of code, but less code to care about for each step: you brain is happy.

Tests get better too. I can now have a test that says "you should create a receipt and add items to it" (hint: mocks required!). Obviously, the factory will now have to be tested: is the receipt created properly? Same goes for the receipt itself. 

I added a layer of indirection, but the resulting algorithm is simpler, in each and every one of these classes. +1 for the readability because you know where to look if you want to change how a receipt is created. If you don't care, you don't even need to look for it. +1 for test documentation too, I now know they fully document these pieces of code.

This code is not tested!

The code above has not been tested. It looks like java, but who cares.

If I was to do it in TDD, I would probably have come up with better names, better collaborators. I have found that the best way to figure out the proper use of layers is through (mockist) TDD.

If a mock is hard to reason about, it is wrong. An integration test will not put enough pressure on my design to let me know about it. The slice which is being tested will grow, and grow, and grow... until the "in-between" becomes unreadable. What do we do? We split it! We add a layer of I-don't-care-ness, or at least that's what we think we are doing. We are using the code immediately, so it looks easy enough to understand. Here is the catch: how do you know you made the right separation? Is it viable in the long term?

This is where I leave it up to you

What do you think? Do you have to understand each parts before changing an algorithm? How does it add complexity?

I know the example is an over simplification, but in my experience, it scales properly.

Am I the only one to imagine software in my head and see all the moving parts interacting with each other? No I am not crazy, my mother had me tested - Sheldon Cooper.

Présentation: Propulsez votre architectures grâce au TDD et aux Mocks (Agile Montréal 2014)

Publié par Félix-Antoine Bourbonnais le mercredi 12 mars 2014 à 06:00

Cette session a été présentée par Félix-Antoine Bourbonnais à la conférence mensuelle d'Agile Montréal le 12 mars 2014.

Description

Nous savons depuis longtemps que les tests automatisés jouent un rôle important pour les équipes de développement Agile. Bien que la communauté ait découvert depuis un certain temps des pratiques permettant de maximiser l’émergence du design via le TDD, il est rare que l’on présente des astuces concrètes pour obtenir ce bénéfice.

Cette présentation explique comment tirer le maximum de vos tests unitaires et des « mocks ». Nous présenterons, plus particulièrement, le style de TDD « mockiste ». Ainsi, nous verrons comment les mocks peuvent nous aider à concevoir une architecture ayant une meilleure conception orientée objet. 

  • Niveau : Avancé
  • Public cible : Développeurs et architectes

Présentation

 

Diapositives (PDF) 

Code source

 

Le code source de la démonstration est disponible pour téléchargement:
https://github.com/fbourbonnais/propulsez-architecture-tdd-mocks

 

Autres billets

 

Jasmine partie 3 : Gestion D'événements Asynchrones

Publié par Jean-Nicolas Viens le jeudi 25 juillet 2013 à 07:46

Ceci est le dernier, mais non le moindre, d’une série de trois articles à propos de Jasmine. Je vous invite à regarder d’abord le premier et le deuxième article avant celui-ci. Nous continuerons avec le concept de mocks vu lors de la deuxième partie afin de pouvoir contrôler le temps! Cela nous permettera, entre autres, de tester des comportements répétés (à toutes les X secondes), ou encore d’accélerer des effets graphiques pour améliorer les performances de nos tests.

Tester des comportements asynchrones

Il existe plusieurs cas de figures où l’on veut attendre la fin d’une action avant d’en valider le résultat. Par exemple, on peut vouloir attendre la fin d’un appel AJAX avant de s’assurer que le contenu d’un conteneur a été changé, ou qu’une fonction a été appelée. Cependant, nous allons profiter de ces exemples pour introduire une extension à Jasmine : jasmine-jquery. Celle-ci nous permet d’utiliser la syntaxe expect($object_jquery), en plus de plusieurs nouvelles méthodes pour valider le contenu de cet objet jQuery. Par exemple, supposons que nous avons un <div> avec fenetre comme id :

expect($("#fenetre")).toBeVisible();

Vous trouverez toutes les informations pertinentes sur ces nouvelles méthodes sur la page github de cette extension. Nous n’élaborerons pas sur ce sujet ici, mais sachez que cette extension permet également l’utilisation de fixtures HTML, de même que l’utilisation de mocks pour les événements.

Illustrons la valeur de jasmine-query et des mocks dans un cas simple mais fort typique en développement web. On veut s’assurer que lorsque le bouton “fermer” de notre fenêtre est appelé, alors la fenêtre se ferme et une autre s’ouvre. Comment vérifier cela? Simplement en vérifiant si la fenêtre #2 est maintenant visible après la fermeture de la fenêtre #1. Si la fenêtre se ferme instantanément, ce n’est peut-être pas un problème, mais qu’arrive-t-il si la fenêtre se ferme avec une jolie animation? Et bien, tout au long de l’animation elle sera visible et notre test échouera!

Jasmine nous permet de séquencer des blocs de code avec la méthode runs(). Ceux-ci seront simplement exécutés dans leur entièreté les uns après les autres dans l’ordre. On peut également ajouter un bloc waitsFor() qui sera exécuté tant et aussi longtemps que celui-ci ne retournera pas vrai ou qu’un timeout ne sera pas atteint. Un bloc runs() ne sera jamais executé tant et aussi longtemps que le bloc waitsFor() qui le précède ne retournera pas true. Tout cela est certainement plus clair avec un exemple :

Notez la structure de ce dernier exemple:

  1. Un bloc de code runs() qui se termine par un appel asynchrone (en jQuery, la fonction animate retourne avant d’avoir complété l’animation en question)
  2. Un bloc de code waitsFor() qui exprime une condition à attendre (note: le timeout est optionnel)
  3. Un bloc de code runs() avec une assertion

Le seul problème de cette technique est que le test est très verbeux, il y a beaucoup de bruit pour rien. Le problème peut être mitigé par l’utilisation d’un langage tel le coffeescript. Voici le même exemple, écrit en coffeescript :

Cependant, il est à noter que ce genre de test n’est pas souvent nécessaire. Dans ce cas particulier, il aurait été préférable de désactiver toutes les animations jQuery tout simplement. On peut faire cela avec cette ligne de code :

jQuery.fx.off = true;

Ce qui a pour effet que toutes les animations sont instantanées.

Bien qu’il est pratique de pouvoir faire tout cela, il est généralement souhaitable de ne pas (ou peu) se coupler du DOM lors de tests Javascript, mais cela sort de la portée de cet article.

Contrôler le temps

Jasmine nous permet également de contrôler le temps en utilisant des mocks sur les fonctions setTimeout et setInterval du Javascript. Heureusement, cette complexité est encapsulée pour nous. Nous avons simplement à dire à Jasmine d’utiliser un mock pour le temps, ce qui fait qu’aucune fonction passée à setTimeout et setInterval ne sera executée tant qu’on ne l’aura pas demandé. Le temps sera “gelé” et nous pourrons alors avancer le temps X secondes à la fois, selon les besoins du test. Pour dire à Jasmine d’utiliser un mock pour gérer le temps, on utilise :

jasmine.Clock.useMock();

Cela installe le mock et s’assure de le désinstaller après chaque test. On peut donc l’utiliser sans danger de collision avec un autre test. On peut également l’utiliser dans le beforeEach(). Par la suite, on utilise la fonction tick(<millisecondes>) pour faire avancer le temps. Voici un exemple typique d’un test :

jasmine.Clock.useMock();
// -> action qui utilise le setTimeout ou setInterval
jasmine.Clock.tick("nombre en millisecondes")
// -> validation que l'action a été exécutée

Il est également possible d’utiliser d’autres fonctions de Clock, dont voici les plus utilisées :

  • reset : retourne le compteur à 0
  • installMock : comme useMock(), mais ne désinstalle pas automatiquement le mock après le test. Il est généralement conseillé d’utiliser useMock().
  • uninstallMock : retire le mock manuellement. Attention, si vous avez utilisé useMock(), vous risquez d’avoir une exception vu que uninstallMock() sera appelé automatiquement une 2e fois.
  • isInstalled : retourne vrai ou faux, selon que le mock pour le temps est installé
  • assertInstalled : même chose, mais lance une exception de type Error

Voici un exemple complet, avec une fonction qui valide l’état d’une session d’un usager à chaque seconde.

Il est possible de faire exécuter instantanément des animations jQuery avec cette technique, mais cela requiert de placer ce bout de code avant de charger jQuery :

window.webkitRequestAnimationFrame = null;
window.mozRequestAnimationFrame = null;
window.oRequestAnimationFrame = null;
window.requestAnimationFrame = null;

Ce code a pour but de désactiver la gestion de “frames” dans le navigateur puisque jQuery utilise cette fonction plutôt que setTimeout désormais. On peut par la suite exécuter pas-à-pas une animation.

Il est pertinent de noter aussi que l’extension jasmine-ajax utilise le même principe pour détourner les appels AJAX, soit :

jasmine.Ajax.useMock();

Je n’entrerai pas dans les détails de cette extension, mais elle vaut certainement le coup d’oeil!

Conclusion

Bien que nous ayons vu une grande partie des fonctionnalités qu’offre Jasmine, plusieurs questions restent. Il y a beaucoup de facteurs à prendre en compte lors de la conception de tests Javascript, principalement : quoi tester? Il faut éviter de trop se coupler sur le DOM et se concentrer sur la logique d’affaires qui se retrouve du côté du client. Il y a aussi plusieurs pièges à éviter, et tout cela fera vraisemblablement partie d’un prochain article sur les tests Javascript. Il faut mentionner également que mon collègue Vincent a produit un article de même nature que celui-ci sur qUnit, outil de tests offert par les développeurs de jQuery.

Cette série d’articles à propos de Jasmine est terminée en soi, mais il reste un sujet à traiter : l’intégration avec nos outils. Dans un prochain article, vous pourrez voir comment intégrer Jasmine.js à Visual Studio, à votre build dans TFS et à une application Rails.

Jasmine partie 2 : Les mocks

Publié par Jean-Nicolas Viens le mardi 23 juillet 2013 à 14:26

Pour faire suite à l’article précédent à propos de Jasmine, voici un nouvel aspect de cet outil : les mocks. Ceux-ci nous permettent de tester un composant de notre code en isolation. On peut ainsi valider l’interaction avec un autre composant ou forcer une réaction de la part de ce composant. Tout cela avec la syntaxe fluente de Jasmine :

spyOn(conversion, "livreEnKilo").andReturn(10);

Utilisation des mocks

Vocabulaire : Jasmine offre la méthode spyOn(); et utilise le terme “espion” pour les objets ainsi crées. Cependant, ces objets sont selon moi des mocks en réalité. J’utiliserai donc le terme mock dans cet article. Martin Fowler a un article sur les différences entre les stubs et les mocks qui est très complet. Il y a aussi cette réponse sur stackoverflow qui résume bien les différences.

Utilisant la flexibilité d’un langage dynamiquement typé tel le Javascript, Jasmine nous propose une façon simple et élégante de créer des mocks. Voici un exemple :

spyOn(objet, "méthode").comportement()

On utilise la méthode spyOn en lui passant l’instance de l’objet à “mocker” ainsi que le nom dela méthode sous forme de string. On peut également associer un comportement à notre mock avec une de ces méthodes :

  • par défaut : la méthode est sans comportement et retourne toujours undefined.
  • andCallThrough() : Installer le mock, mais utiliser la méthode originale lors de l’appel
  • andReturn(<retour>) : Retourner cette valeur à l’appel de la méthode
  • andCallFake(<factice>) : appeler la méthode sur un second objet factice

Si vous voulez “mocker” une méthode globale (ce qui devrait être rare), rappelez-vous que tout objet global est en fait à l’intérieur de la variable window. Vous pouvez donc utiliser, par exemple, spyOn(window, "alert”); .

Maintenant, comment valider qu’il y a eu une interaction avec un mock? Avec la méthode expect(), comme pour les assertions! Il suffit de passer la fonction “mockée” en paramètre et utiliser soit toHaveBeenCalled() ou toHaveBeenCalledWith(<paramètres>). Comment cela fonctionne-t-il? Il faut simplement se rappeler que objet.action() appelle la fonction action() sur objet et retourne le résultat de celle-ci, alors que objet.action retourne la fonction action elle-même sans l’exécuter. Jasmine tire donc profit de cette fonctionnalité du Javascript pour nous permettre de faire :

spyOn(window, "alert");
alert("test");
expect(window.alert).toHaveBeenCalledWith("test");

Note : toHaveBeenCalledWith() valide également le type du paramètre, donc “10” (string) et 10 (integer) ne sont pas pareils ici.

Attention, il est fréquent de voir expect(window.alert()), ce qui ne fonctionne pas puisque le retour de la fonction alert n’est pas un mock!

On peut également valider seulement le type du paramètre :

spyOn(window, "alert");
alert("test");
expect(window.alert).toHaveBeenCalledWith(jasmine.any(String));

Note : Tout type ou constructeur est valide ici (Function, String et Number sont sûrement les plus utilisés)

Nous pouvons donc valider que le paramètre est exactement la valeur voulue ou du bon type, mais il est impossible de valider une condition sur ce paramètre. En fait, Jasmine n’offre pas cette fonctionnalité “out of the box”, mais l’outil est assez bien construit pour que ce soit trivial d’implémenter ce comportement. J’ai donc écrit ce plugin qui permet de valider un paramètre de la façon suivante :

expect(window.alert).toHaveBeenCalledWith(jasmine.Arg().contains("est"));

Nous ne nous y attarderons pas dans cet article, mais notez qu’il est également possible de créer un mock à la volée avec createSpy() et createSpyObj().

Pour terminer, voici une démonstration sur les mocks.

Cet exemple utilise également la méthode beforeEach(), dont l’utilité devrait être claire à ceux habitués aux outils de la famille xUnit. Cette méthode sera exécutée une fois avant chaque spécification. Notez que les beforeEach() sont cumulatifs lorsque l’on dispose de spécifications imbriquées. Bien entendu, il existe également afterEach().

Une astuce intéressante est de remarquer que tous les appels AJAX dans jQuery passent par la méthode $.ajax(), ce qui rend les choses faciles à tester. En voici un exemple :

Conclusion

En terminant, sachez que vous pouvez également utiliser Sinon.js, qui est un framework de mocks en Javascript. Celui-ci cohabite bien avec Jasmine, si vous avez une préférence pour Sinon.js.

Dans le troisième et dernier billet à propos de Jasmine, nous utiliserons les mocks dans un but étrange, mais puissant : contrôler le temps!

Lire le billet suivant

Archive