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.

DDD vs Clean Architecture: un trop court résumé...

Publié par Félix-Antoine Bourbonnais le jeudi 7 août 2014 à 00:00

DDD vd Clean Architecture

La semaine dernière, Anis Berejeb a publié un billet sur l’architecture par cas d’utilisation. Suite à mon commentaire, il y a eu un petit débat concernant la différence entre DDD (Domain-Driven Design) et Clean Architecture. La question portait surtout sur la place des cas d’utilisation (Jocobson) en DDD.

Mon commentaire se résumait à dire que la conception par les cas d’utilisation n’est pas la seule façon d’atteindre les objectifs de séparation de concepts, inversion des dépendances, séparation de la logique de la persistance et de l’affichage, etc.

Disons-le immédiatement, il est difficile de tracer une ligne claire, car DDD et Clean Architecture sont, sur plusieurs aspects, orthogonaux. Mais risquons-nous... 

La portée  

DDD est de plus haut niveau que Clean Architecture qui est plus technique. Ainsi, une bonne partie de Clean Architecture se trouve à être un moyen architectural, parmi d’autres, pour appliquer la philosophie DDD. 

DDD vise la modélisation de problèmes complexes dont la logique d’affaires est importante. DDD va aller toucher la modélisation du domaine et la communication avec des experts du domaine (établissement d’un langage commun, contextes ...). C’est donc beaucoup plus qu’un modèle architectural (technique), c’est aussi une philosophie de modélisation, voire une façon de réfléchir.

L’accent 

Dans l’approche, la principale différence en est une d’accent (focus) lors de la conception. CleanArchitecture focalise sur les cas d’utilisation alors que DDD focalise sur la modélisation du domaine (les entités, valeurs, …).

Ainsi, les praticiens de Clean Architecture semblent plus enclins à encapsuler les cas d’utilisation dans un « Interactor ». DDD risque de préférer intégrer les cas d'utilisation au domaine (dans les entités, Domain Service ...) puisque c’est là qu’est portée toute l'attention.

Où placer la logique

En CleanArchitecture la question sera possiblement: « est-ce indépendant de l’application? » La tendance sera donc d'en mettre plus dans « l’Interactor » et de mettre dans le modèle seulement ce qui est indépendant de manière évidente.

DDD a aussi le même concept (dépendant ou indépendant), mais aura tendance à poser la question à l’inverse en considérant d’abord le domaine, ce qui va avoir comme conséquence de placer d’avantage de code dans le domaine (notamment dans les entités) et de fragmenter le cas d’utilisation. DDD a aussi le concept du Bounded Context qui entrera en jeu dans cette décision en limitant les dérives de l'approche.

À l’inverse, Clean Architecture aura tendance à créer des entités avec moins de logique et d’en placer plus dans les « Interactors ». Évidemment tout cela est du cas par cas, mais on parle de tendance naturelle ici...

À noter qu’avec DDD, la couche d’Application Service en est une d’orchestration, mais pour les considérations plus techniques (ex.: les transactions) ou encore pour orchestrer sur plusieurs contextes (Bounded Contexts). On pourrait la voir comme une couche de cas d’utilisation, mais avec moins de détails (rappelez-vous que la définition et le découpage ne sont pas exactement pareils dans les deux approches).

Les services du domaine (Domain Service) auront aussi un rôle d’orchestration, mais pour ce qui ne peut être intégré à une entité.

Exemple 

Prenons l’exemple du paiement d’une commande illustré par ce graphique qui montre une conception plus orientée cas d’utilisation (Clean Architecture). En DDD, il serait plus probable de voir la méthode « payOrder » initialement placée dans l’entité « Order ». Cela ne veut pas dire que ça ne changerait pas dans le temps par contre. 

Pour un exemple en DDD, consultez le code de l’application DDD Samplecette formation en ligne ou encore le livre de Vaughn Vernon.

Avantages et inconvénients

Voici quelques avantages, inconvénients et risques naturels associés au «focus» différent. 

Quelques avantages de la modélisation par cas d’utilisation (Clean Architecture):

  • Cas d'utilisation identifiables facilement et clairement énoncés;
  • Facile de déployer les entités indépendamment et de les réutiliser dans plusieurs applications de l’entreprise (note: DDD a les concepts de Bounded Context et Shared Kernel pour balancer);
  • Plus facile de comprendre le cas d’utilisation rapidement.

Quelques risques:

  • Domaine moins structuré et plus « vide » ;
  • Réutilisation de la logique pour plusieurs cas d’utilisation plus complexe (ou dangereuse);
  • Danger de tomber dans un domaine anémique ou de faire de la programmation procédurale involontairement;
  • Danger de concevoir par les données les entités plutôt que d’avoir un domaine consistant représentant bien la logique du domaine d’affaires;
  • Moins d'outils concernant la manière d’arriver à modéliser son domaine, d’avoir un langage commun, etc;
  • Risque de complexité plus élevé dans « l’Interactor », plutôt que d’abstraire cette complexité dans le design du domaine.

 Et inversement pour DDD… Évidemment, comme toujours, tout est une question de compromis en fonction de son contexte. C’est ça l’art du développement logiciel !

Résumé et recommandations: complémentarité!

En résumé, tout est une question d’accent (focus). DDD focalise sur le domaine, CleanArchitecture sur les cas d’utilisation. Non pas que DDD ne considère pas les cas d’utilisation, mais ce n’est pas sur ça qu’est placé l'accent!

Si vous êtes dans un domaine de TI avec beaucoup de règles d’affaires, DDD vous sera probablement très utile pour modéliser le domaine, le découper, établir votre langage commun, etc. Vous pouvez certainement y combiner les techniques de Clean Architecture pour ce qui est de l’inversion des dépendances, de l’interfaçage, etc. 

À mon avis, l’important est de se souvenir qu’ils peuvent êtres complémentaires et que ce sont des techniques, pratiques et concepts à avoir dans sa trousse à outils. 

Donc comme le dit Bob Martin lui-même: « It's not really important which of the two [Use Case, Domain] drives […] It is the designers job, in each case, to prevent that muddle. » [2]. 

Après tout, les deux poursuivent le même but ultime! Tous s’entendent sur la nécessité de séparer la logique d’affaires des préoccupations techniques, l’inversion des dépendances, de rendre visible le domaine d’affaires plutôt que les frameworks et outils, etc.

Références 

[1] Comparaison selon Bob Martin:
https://groups.google.com/d/msg/clean-code-discussion/oEFEWq8qdFQ/i0gsi3eU5VoJ

[2] Lequel choisir selon Bob Martin:
https://groups.google.com/d/msg/clean-code-discussion/xm8QtaYwl3k/LWgFBs3erd0J

[3] Réponse connexe concernant l’application layer en DDD:
https://groups.yahoo.com/neo/groups/domaindrivendesign/conversations/messages/24325

Classique du développement logiciel: Working Effectively with Legacy Code

Publié par David Beaumier le jeudi 21 novembre 2013 à 13:19

Si on faisait une compilation des meilleurs livres de référence en développement logiciel il ne fait aucun doute pour moi que le livre de Michael C. Feathers en ferait partie. Intitulé Working Effectively with Legacy Code, ce bouquin explique comment approcher le développement dans une application existante afin d'en améliorer la maintenabilité. C'est un guide pratique pour quiconque travaille dans du code patrimonial.

J'entends déjà certaines personnes se dire: "Oh là David, je ne travaille pas dans du code patrimonial moi. Je développe une application en .NET". Lorsque Feathers parle de code patrimonial il fait en fait référence à tout code qui nous fait n'a pas de tests automatisés (idéalement unitaire). Peu importe que ce soit écrit en Cobol, en Delphi, en Java ou en Ruby. Si votre code n'est pas testé, c'est du code patrimonial! Désolé…

Couverture-WorkingEffectivlyWithLegacyCodeCode without test is bad code. It doesn’t matter how well written it is; it doesn’t matter how pretty or object-oriented or well-encapsulated it is. With tests, we can change the behavior of our code quickly and verifiably. Without them, we don’t know if our code is getting better or worst.

Je possède ce livre depuis nombre d'années, mais ça faisait un bout de temps que je ne l'avais pas ouvert. Merci à mes collègues Félix-Antoine et Pascal pour l'avoir remis au goût du jour avec leur atelier sur la dette technique présenté à l'Agile Tour. La dette technique est, bien souvent, assez présente dans les logiciels patrimoniaux.

Comme l'ont souligné mes collègues durant leur conférence, ce livre regorge de trucs pratiques pour prendre (ou reprendre) le contrôle du code source d'une application qui manque cruellement de filets de sûreté (lire de tests). En plus, pas besoin de tout révolutionner d'un coup! Il est possible d'y aller graduellement, étape par étape. Feathers explique fort bien comment vous pouvez y prendre et réussir, espérons-le, à payer la dette qui s'accumule depuis plusieurs années.

J'en profite aussi pour souligner que Feathers présente régulièrement dans des conférences internationales. Je me permet de vous suggérer quelques apparitions disponibles en vidéo:

J'espère que le prochain livre de Michael sera aussi intéressant que son premier (il doit sentir une certaines pression). D'ici là, bon développement!

 

La complexité involontaire

Publié par David Beaumier le vendredi 26 juillet 2013 à 17:07

Comme développeur nous avons tous à lire et surtout, à comprendre le code écrit par d'autres personnes. Que ce soit pour modifier une fonctionnalité existante ou pour effectuer une forme de rétro-ingénierie, il est primordial de ne pas avoir à s'arracher les cheveux de la tête pour comprendre l'intention du code. Or, hélas, trop souvent ce n'est pas le cas.

Il peut y avoir plusieurs causes à cette situation : méthode trop longue, mauvais nommage, non-respect des règles de nomenclature, etc. Ceci dit, je crois que bien souvent le code ne s’est pas retrouvé dans cet état dès le départ. J’aurais plutôt tendance à blâmer la vilaine complexité involontaire. Non, il ne s’agit pas d’une maladie contagieuse ou d’un mal contre lequel votre anti-virus pourra vous protéger. C’est plutôt comme la moisissure sur le fromage : ça apparaît après un certain temps et plus rapidement si on le manipule fréquemment sans prendre les précautions nécessaires!

Prenons un exemple dans le domaine d’affaires de l’assurance pour illustrer le processus. Une personne a créé au départ une structure de contrôle pour ne traiter que les polices à renouveler au cours des 30 jours à venir. Assez simple, n’est-ce pas?

if (p.DateRenouvellement < Now.AddDays(30)) {
  TraiterRenouvellement(p);
}

Quelques temps plus tard, un collègue a implémenté une nouvelle règle d’affaires dans le traitement pour répondre à un besoin de l’entreprise.

if (p.DateRenouvellement < Now.AddDays(30)) {
  if (Date.Now.Year - p.Client.EstClientDepuis.Year > 10) {
    var rabais = 0.1;
    TraiterRenouvellement(p, rabais);
  }
Else {
  TraiterRenouvellement(p, 0);
}

Par la suite, une autre demande est venue s’ajouter et une autre personne est venue modifier la fonction.

if (p.DateRenouvellement < Now.AddDays(30)) {
  if (Date.Now.Year - p.Client.EstClientDepuis.Year > 10) {
    var rabais = (Date.Now.Year - p.Client.EstClientDepuis.Year)/100;
    TraiterRenouvellement(p, rabais);
} }

Et ainsi de suite, jusqu’à ce qu’on ait une méthode de 125 lignes, ou même plus! Bien qu’avec le recul on puisse identifier différents éléments qui auraient pu être faits pour réduire la complexité, le plus souvent on ne peut accuser les gens de mauvaise foi. C’est plutôt une spirale sournoise dans laquelle l’équipe s’est engagée s’en trop s’en rendre compte… Jusqu’au jour où on ne s’y retrouve plus!

Favoriser la lisibilité du code source

Barbara Liskov (bien connue pour avoir donné son nom au Liskov Substitution Principle) mentionnait dans une récente entrevue que l’on devrait accorder plus d'importance à la lisibilité du code qu'à son écriture (read-ability VS write-ability). Les compilateurs, éditeurs de code et autres outils de développement offrent une panoplie de fonctions qui tendent à réduire le nombre de lignes de code source. Cependant, est-ce qu'on y gagne réellement au change?

Je pense, par exemple, à la fonctionnalité de Resharper qui permet de convertir une boucle en une expression LINQ. Oui, il y a des cas où cela peut-être bénéfique, mais il y a aussi bien des cas où, en fin de compte, on aura perdu une part de lisibilité du code dans l'opération.

Quelques façons d’éviter la complexité involontaire

Il existe différentes façons de s’entraîner à développer sa capacité à détecter les pièges de la complexité involontaire. De façon générale il faut débuter par accepter de sortir de sa zone de confort et graduellement apprendre à prendre un peu de recul et évaluer le code que l’on produit ou modifie.

Plusieurs excellents ouvrages de référence ont été publiés sur le sujet. C’est un bon point de départ pour quiconque veut approfondir ces techniques. Voici quelques suggestions :

Revues de conception

Pourquoi ne pas ajouter la notion de revue de conception ou de test par un pair à la définition d’avoir terminé de votre équipe? Chacun s’assure qu’au moins une autre paire d’yeux a jugé son travail et en a validé la conformité aux standards de l’équipe. C’est une étape qui permet de détecter rapidement des éléments qui manquent de clarté et qui apparaissent ambigus pour une autre personne. Ça ne veut pas dire que vos collègues ont forcément raison et vous tort, mais ça vaut probablement la peine de tenir une brève discussion sur les aspects qui ont été soulevés durant la revue.

Bien que je trouve que la revue par un pair apporte la meilleure valeur pour l’équipe, je crois que l’on peut aussi faire preuve d’autocritique. Par exemple, pourquoi ne pas prendre un peu de temps chaque matin pour repasser le code qu’on a écrit la veille? Par comparaison, un ébéniste prend habituellement le temps de faire un montage à sec avant de coller les assemblages d’un meuble. Cela lui permet de vérifier toute anomalie et la corriger avant de faire le collage final. Oui, ça prend un peu de temps, mais c’est une étape qui lui permet de s’éviter d’éventuels ennuis.

Métriques du code

En plus des évaluations qualitatives dont on vient de discuter, la complexité du code peut se mesurer de façon quantitative. Il existe plusieurs mesures, telles que la complexité cyclomatique, l’index de maintenabilité, le niveau de profondeur des structures de contrôle et la cohésion.

Il existe aussi des outils d’analyse statique du code qui permettent d’assurer le respect des règles de développement de l’équipe (nomenclature, etc.). Plusieurs de ces outils sont gratuits (tel que Source Monitor) ou même intégrés directement aux IDEs (comme le propose Visual Studio).

Conclusion

Développer et maintenir un système c’est comme une partie d’échecs. On doit penser quelques coups d’avance, tout en étant capable de réagir aux mouvements de l’adversaire. Il revient à chacun de trouver le juste équilibre entre perfection et livraison, mais il convient, à mon avis, d’identifier le seuil minimal que vous vous engagez à respecter. Lorsque le contexte ne vous permet pas de rencontrer ce seuil, n’hésitez pas à rendre cette situation visible à l’équipe et à discuter de la possibilité d’ajouter un élément au carnet de produit pour revoir cette partie du code source à un moment qui sera plus opportun (tel qu’au début du prochain cycle de livraison).

J’ai un jour entendu une personne dire ceci : "Écrivez votre code comme si celui ou celle qui en fera la maintenance était un psychopathe dangereux qui connaît votre adresse". C'est peut-être un peu extrême, mais ça m'a toujours forcé à me mettre dans la peau de la personne qui allait devoir comprendre mon code en m'efforçant de réduire le plus possible la complexité de celui-ci. Bon développement!

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