AngularJS : tests unitaires et d’interface utilisateur

This entry was posted by on Dimanche, 8 juillet, 2012 at

Si il y a bien un élément différenciateur par rapport aux autres framework JS c’est la capacité d’AngularJS à mettre en oeuvre les tests. C’est normal dirais-je puisque le framework a été développé en intégrant dès le départ cet aspect fondamental. JavaScript est un langage dynamiquement typé ce qui le rend simple à écrire mais qui permet aussi d’écrire des erreurs que le compilateur n’indiquera pas. Il est donc d’autant plus important lorsqu’on utilise ce langage de bien le tester.
Mais AngularJS n’est pas magique non plus, comme tous ceux qui ont mis en place des tests le savent, l’important est de penser son code pour les tests. La où il faudra être vigilant est de ne pas faire appel à des objets ou des méthodes sans savoir si ils seront facilement testable. Prenons l’exemple d’un controleur, si au sein de celui-ci des appels sont fait au DOM via le $ de jQuery par exemple, ce code deviendra tout de suite complexe à tester.
Les outils actuels malheureusement ne vont pas nous permettre comme en Java de faire facilement du TDD, où en écrivant mon test l’IDE va proposer la création des classes et des méthodes. Rien n’empêche cependant de le faire.
En théorie AngularJS n’impose pas de framework de test et vous pouvez tester avec vos outils mais l’équipe AngularJS a développé des bibliothèques pour faciliter l’écriture de tests et ceux-ci se basent sur Jasmine et JsTestDriver (dont Misko est co-auteur) :

  • angular-mocks : qui regroupe un ensemble de mock de service de base : $log, $httpBackend, $timeout, $exceptionHandler et aussi des méthodes pour faciliter l’écriture de tests : module (pour charger un module), inject (pour injecter des dépendances)
  • angular-scenario : une extension de Jasmine pour permettre des tests d’interface utilisateur (end to end testing), l’idée est de fournir un DSL facilitant l’écriture de ce type de test.

Passons à la pratique.

Exécution manuelle Jasmine+JsTestDriver

Avec angular-seed des scripts sont fournis pour mettre en place les tests. Il suffit de lancer scripts/test-server.[sh|bat] pour démarrer JsTestDriver et d’ouvrir une page sur chaque navigateur que vous voulez tester à l’adresse http://localhost:9876/. Puis de lancer les tests manuellement avec scripts/test.[sh|bat]. Tous les tests déclarés dans le répertoire test/unit seront exécutés. Vous pouvez modifier ce comportement en ciblant certains test ou d’autres répertoires via le fichier de configuration config/jsTesDriver.conf

Exécution dans un IDE : Eclipse et IntelliJ/WebStorm

JsTestDriver fourni des plugins pour Eclipse et IntelliJ. Vous pouvez donc exécuter vos tests dans votre IDE préféré plutôt qu’en ligne de commande. Attention cependant si vous utilisez angular-seed à revoir les chemins déclarés dans config/jsTesDriver.conf qui ne correspondent pas à une exécution dans un IDE.

Test d’un controleur

Partons du code suivant : une simple liste de nom accompagné d’un bouton de suppression et suivi d’un champ texte avec un bouton pour ajouter de nouveau nom :

Nous devons tester le controleur pour s’assurer que l’appel de la méthode addPatient() ajoute bien un patient dans la liste. Notez l’utilisation de module(‘myApp’) qui va charger le module correspondant (un air de Guice) ainsi que l’utilisation de inject($rootScope, $controller). inject(…) autorise autant d’argument que vous voulez, ces arguments doivent faire référence à des objets connus du module et sont identifiés par leur nom : inject(monService) ne marchera pas si vous n’avez pas déclaré de service monService. De même inject($controleur) ne marchera pas car le service s’appelle $controller.

Test d’une directive

Reprenons ici la directive ui-date de l’article précédent et détaillons la. De même que ci-dessus module() et inject() sont utilisés pour injecter nos dépendances. Notons par contre que l’implémentation du datepicker jQuery-UI est pensée pour les tests car elle permet d’interagir sur le composant via des méthodes qui simulent les actions utilisateur. C’est un point important pour le choix d’une bibliothèque externe que l’on veut encapsuler dans une directive.

Test d’interface utilisateur ou end to end testing

Avec le E2E nous allons pouvoir simuler des interactions utilisateurs avec l’interface graphique. Alors vous me direz “et Selenium alors ?”, eh bien certains ont essayés
mais c’est tout de même plus complexe. De plus Selenium ne connait pas le fonctionnement d’AngularJS et les essais menés ont montrés des problèmes de lenteurs. Misko résume dans le forum pourquoi ils n’ont pas utilisé Selenium.
D’autre part l’utilisation des selecteurs jQuery pour cibler les balises est très pratique et simple à écrire.
L’exemple ci-dessous montre comment, à partir du code précédent, tester l’ajout d’un nom dans la liste.

Pour exécuter ce test dans angular-seed il est nécessaire de l’écrire dans test/e2e/scenario.js. Ensuite il faut que votre site soit démarré et il suffit alors d’appeler l’URL http://localhost:port/test/e2e/runner.html dans votre navigateur. Ce qui donne le rendu suivant :

Simulation d’un appel AJAX

Pour pouvoir tester du code faisant des appels serveur, AngularJS fourni un mock du service $http (que nous verrons plus en détail dans un prochain article). Ainsi les tests peuvent être exécuté indépendamment du serveur. Reprenons notre exemple précédent et faisons appel à la resource data/patients.json pour récupérer les noms des patients :

où data/patients.json renvoi

Pour réaliser le test il

est nécessaire d’injecter l’objet $httpBackend. Celui-ci va alors intercepter les appels du service $http pour renvoyer des données de test. Dans le test suivant la méthode when() va indiquer ce que doit renvoyer la resource data/patients.json sera :

$httpBackend fourni 2 méthodes :

  • when() : qui va définir de manière globale ce que renvoi une resource, il n’est pas obligatoire de préciser la resource
  • expect() : qui va définir de manière manière précise ce que renvoi une resource en particulier

A noter aussi la méthode flush(), les requêtes serveur étant asynchrone ceci ne doit pas être le cas dans l’execution des tests. La méthode flush() force donc la requête à s’exécuter et à simuler un mode synchrone.

Testacular

Pour exécuter les tests en temps réel (genre infinitest) et sur différents navigateurs Vojta à développé Testacular : un moteur d’exécution sous node.js intégrable dans IntelliJ/WebStorm. La trace des logs permet de voire la pile d’execution et la référence à son test. IntelliJ/WebStorm rend cela cliquable et vous permet d’aller directement dans le fichier concerné.
Je vous laisse découvrir cet outil pratique à travers cette video.

Comments are closed.