Publié par
Il y a 4 années · 9 minutes · Craft, Front

Les nouveaux frameworks de tests d’UI

Introduction

Comment tester mon application web dès la phase de développement, de manière industrialisable ?

Les interfaces graphiques ont longtemps été boudées par les tests automatisés. Si la plupart des applications étaient testées manuellement, on connaissait aussi un outil de tests industrialisés : Selenium avec son API plutôt complète mais également sa lourdeur. Rappelez-vous, un serveur Jetty devait être démarré pour lancer nos tests, l’intégration au job Maven pour les projets Java n’était pas triviale, et il était difficile de continuer à développer sur nos machines quand la RAM était monopolisée et l’écran inondé de nouvelles fenêtres navigateur pour effectuer les tests en conditions réelles.

Aujourd’hui, un profil développeur préférera des outils de tests d’UI simples et efficaces, en troquant volontiers les plugins UI Sélénium contre la rapidité d’un navigateur headless et des résultats de tests qui sortent brut de décoffrage dans la console… quitte à laisser les tests multinavigateurs à des équipes dédiées comme la QA. Les tests d’UI dont nous parlons, écrits et joués en phase de développement, ont pour vocation de garantir les scénarios de navigation. Pour les tests visuels, il faudra plutôt se tourner vers des solutions de tests cross-browsers, qui feront inéluctablement intervenir un œil humain.

Avec la véritable expansion des frameworks JavaScript, les outils des développeurs front ont également évolué et proposent des tests d’interface faciles à mettre en œuvre.

Dans cet article, je vous propose de découvrir deux de ces frameworks de tests modernes, en suggérant plusieurs critères de comparaison.

CasperJS

Outil à part entière, CasperJS exécute vos scénarios de tests dans un navigateur headless et résume l’état de chacun des tests en sortie. Il gère les suites de tests, l’affichage custom de la sortie console, la gestion des paramètres de lancement de la commande…

Dans cet article, on détaillera l’utilisation de la version stable actuelle (1.0).

Installation

Il faudra installer le navigateur headless PhantomJS en téléchargeant ses binaires, lui-même dépendant de librairies GCC. Si jamais vous n’avez pas l’environnement adéquat, il est possible de s’en sortir en buildant PhantomJS depuis les sources.

Ce prérequis levé, l’exécutable de Casper est disponible en téléchargeant les archives ici, ou via Homebrew sur OSX.

L’API Casper

Elle est très complète et permet via l’objet casper, de simuler les actions principales d’un utilisateur : se rendre sur une page via l’url, inspecter le texte ou les attributs d’un élément, remplir un formulaire, cliquer sur les éléments, ou encore attendre qu’un évènement se déclenche (apparition d’une pop-in, mise à jour d’une liste d’auto-complétion…).

Si malgré la documentation très complète vous ne trouvez pas votre bonheur dans les fonctions proposées, il est possible d’utiliser la méthode evaluate pour simuler l’exécution d’un code JavaScript dans le navigateur, rendant presque tout possible pour peu que votre application expose jQuery et ses méthodes utilitaires.

L’API de test

Mais les actions navigateur ne suffisent pas, vous aurez besoin de tester la réaction de l’UI et vérifier les comportements attendus. Cette API, en plus des outils de RegExp habituels, effectue des tests sur des propriétés graphiques : l’existence ou la visibilité d’un élément, le statut de la page et son titre, l’existence d’un texte dans la page, … Vous pouvez également jouer avec les différents niveaux de logs et structurer vos tests joliment en utilisant les méthodes comment, info, pass, fail et error.

Un affichage exhaustif de toutes les assertions

Pour chaque test, il vous est possible de préciser l’assertion à afficher en cas de succès, et Casper se charge d’enrichir la description du test en cas d’échec.

Cet exemple de test :

this.test.assertTitle('Google', 'page title is correct');

Rend dans la console :

Ou bien en cas d’échec :

Les suites de tests

CasperJS permet de lancer plusieurs fichiers de tests grâce à la commande "test" :

casperjs test testfile1.js testfile2.js mytestfolder

Il est alors inutile d’initialiser l’objet casper dans chaque fichier. Cela est fait automatiquement une fois au lancement de la suite de tests.

Finalement, il existe une certaine dépendance entre les tests, du moment où le même objet casper est initialisé une seule fois et réutilisé. Cela est comparable à un navigateur dans lequel on saisirait différentes URLs dans le même onglet, au lieu de fermer et de rouvrir le navigateur à chaque fois.

Les paramètres de commande

Il est possible de passer des options au lancement des tests :

casperjs test testfile1.js --mode=debug --environment=recette

Elles seront prises en compte dans le code grâce à Casper CLI. Par exemple :

var hasEnv = casper.cli.has('environment');
var env = casper.cli.get('environment');
casper.cli.drop('environment');

Exemple de test

casper.start('http://www.google.fr/', function() {
 this.test.assertTitle('Google', 'page title is correct');
});
casper.then(function() {
 this.test.comment('Google links');
 var links = ['Recherche', 'Images', 'Maps', 'Play', 'YouTube', 'Gmail'],
  link;
 for (var i = 0; i < links.length; i++) {
  link = links[i];
  this.test.assertMatch(this.fetchText('#gbzc'), new RegExp(link), '"' + link + '" link exists on page');
 }
});
casper.then(function() {
 this.test.comment('Search form');
 this.test.assertVisible('form', 'a form is here');
 this.fill('form', {q: 'gloubiboulga'});
 this.test.info('type text "gloubiboulga"');
 this.capture('form-filled.png');
 this.test.assert(this.click('form input[name="btnG"]'), 'the submit button is clicked');
});
casper.run(function() {
 this.test.done();
});

Ici, l’instruction capture permet de prendre un instantané du rendu navigateur : pratique pour débugger un test qui ne passe plus.

Résultat :

Pour aller plus loin

Il est possible de lancer les tests dans un build Grunt, grâce au module grunt-casperjs. Les tests sont alors parfaitement intégrés dans le workflow de build du projet.

Compatible avec nodejs, l’instruction require(‘module.js’) est disponible pour importer vos propres modules, ou pour utiliser des bibliothèques publiques.

CasperJS supporte également les tests écrits en CoffeeScript : il se base sur les extensions de fichiers fournis en entrée de la commande.

Une "latest hipstered version" de Casper est déjà disponible en bêta (1.1) et améliore considérablement la facilité d’écriture de tests. Il y a un réel désir d’homogénéiser l’API pour se rapprocher des frameworks de tests unitaires existants, en valorisant le BDD. 

Zombie

En toute modestie, Zombie propose une API très simple d’utilisation et laisse le libre choix du framework de test d’assertions. C’est pour cela que nous nous attarderons moins sur les assertions et les sorties console, qui dépendent du framework de test utilisé.

Installation

L’installation se fait via npm : ultra-pratique, sauf sur les environnements Windows qui réservent quelques surprises. Pour ces derniers, il faudra passer par l’installation d’un shell Unix et de nodejs, en priant pour qu’il n’y ait pas de problèmes de dépendances au moment de lancer le fameux npm install.

De même pour l’utilisation, il faudra installer vos dépendances npm avant de les appeler avec require dans vos tests.

L’API

La liste d’actions proposées par Zombie est plus complète que celle de Casper, et expose directement les méthodes choose, select, et check, pour les éléments de formulaire HTML que l’on croise régulièrement. Cela rend les tests très lisibles et l’on s’abstrait d’une syntaxe technique qui pourrait vite alourdir les tests.

Votre test de formulaire pourrait ressembler à cela :

browser.
  fill("Your Name", "Arm Biter").
  fill("Profession", "Living dead").
  select("Born", "1968").
  uncheck("Send me the newsletter").
  pressButton("Sign me up", function() {
    assert.equal(browser.location.pathname, "/thankyou");
  });

Comme on le voit dans cet exemple, la sélection des éléments du DOM peut se faire par leurs libellés : cela garantie l’abstraction des tests qui peuvent être codés avant de penser à la structuration de la page.

Exemple de test

En utilisant Mocha, on arrive à un test écrit de la sorte :

var Browser = require('zombie'),
 expect = require('expect.js');

browser = new Browser();

describe('Google page', function() {

  before(function(done) {
  browser.visit('http://www.google.fr/', done);
 });
 
 it('should display correct title', function() {
  expect(browser.success).to.be.ok();
        expect(browser.title).to.be('Google');
 });
 
 it('should display links', function() {
  var links = ['Recherche', 'Images', 'Maps', 'Play', 'YouTube', 'Gmail'],
   link;
  for (var i = 0; i < links.length; i++) {
   link = links[i];
         expect(browser.text('#gbzc')).to.contain(link);
  }
 });
 
 it('should display search results', function() {
  browser.
      fill('q', 'gloubiboulga').
      fill('other', 'other').
      pressButton("Recherche Google", function() {
                expect(browser.success).to.be.ok();
      });
 });
 
});

Autres points forts

L’humour ! Le tutoriel en est bourré, vous expliquant l’utilisation de Zombie, depuis la "morsure" jusqu’à l’"infection" et la "chasse"…

Finalement…

Si vous préférez une solution clé en main, avec une façon de faire bien définie dans une documentation claire, préférez CasperJS. L’équipe de développement est active, et l’outil s’améliore continuellement : il vous sera aisé de trouver des réponses à vos questions au sein de la communauté.

Au contraire, déjà formé aux frameworks de tests, avec l’envie d’accéder à des tests d’UI sans changer le reste, vous pouvez adopter ZombieJS qui assure une souplesse d’utilisation qui vous conviendra.

N’ayez pas peur d’affronter des stack traces d’erreur en cas de test en échec.

Le must : CoffeeScript, un langage qui se prête aux tests

coffeescript

Grâce à ce langage léger, on s’affranchit de la syntaxe pour se focaliser sur le fonctionnel. Cela colle parfaitement à l’esprit des tests fonctionnels, dont le but est de tester l’interface sans se soucier des détails d’implémentation.

Exemple d’un test Casper écrit en CoffeeScript :

casper.start 'http://www.google.fr/', ->
  @test.assertTitle 'Google', 'page title is correct'
 
casper.then ->
  @test.comment 'Google links'
  @test.assertMatch @fetchText('#gbzc'), new RegExp(link), "#{link} link exists on page" for link in ['Recherche', 'Images', 'Maps', 'Play', 'YouTube', 'Gmail']
 
casper.then ->
  @test.comment 'Search form'
  @test.assertVisible 'form', 'a form is here'
  @fill 'form',
    q: 'gloubiboulga'
  @test.info 'type text "gloubiboulga"'
  @capture 'form-filled.png'
  @test.assert @click('form input[name="btnG"]'), 'the submit button is clicked'
 
casper.run ->
  @test.done()

Tip

En CoffeeScript, pour partager des variables entre tests, préférez :

root = exports ? this
root.myVar = 'hello'

Et accédez-y simplement en appelant myVar dans vos autres fichiers.

7 thoughts on “Les nouveaux frameworks de tests d’UI”

  1. Publié par chris, Il y a 4 années

    Le lien « lire la suite de l’article » que j’ai recu dans mon flux rss pointe sur seleniumhq.org

    Je pense que c’est un bug dû au fait que la « coupure » est tombée pile sur un hyperlien de l’artticle

  2. Publié par Audrey Pedro, Il y a 4 années

    Merci pour cette remarque, nous allons corriger ce point.

  3. Publié par Sylvain, Il y a 4 années

    J’avoue je suis moyennement convaincu par les arguments contre Selenium.

    Déjà, Selenium supporte phantomjs, donc il est tout à fait possible de ne pas être inondé de nouvelles fenêtres si on fait des tests en local. Qui plus est, même si on utilise CasperJS il faut forcément démarrer un conteneur HTTP (Jetty, NodeJs ou autre) sur lequel l’application va être déployée et donc ce n’est pas un argument. Enfin, l’intégration avec Maven m’a semblé simple, c’est juste des tests d’intégration on ne peut plus basique.

    Donc, au vu de cela, il ne reste que pour moi l’API. Elle semble c’est vrai plus agréable sur CasperJS. Mais il faut voir que Selenium permet de facilement capturer un scénario via un plug-in dans Firefox ce qui fait gagner du temps.

    Enfin, le fait que Selenium supporte aussi tous les navigateurs est un plus et permet de voir ce qu’il se passe réellement si on le souhaite. Enfin, on a toujours le choix de lancer les tests sur plusieurs navigateurs (là par contre ce n’est pas évident via Maven).

    Dans le cadre de projet utilisant AngularJs, Karma (aka testacular) est mieux adapté.

  4. Publié par Sophie Trinh-Khanh, Il y a 4 années

    Merci pour tes arguments, effectivement je ne connaissais pas ces possibilités. A l’époque où j’utilisais Selenium, il n’avait rien de sexy, Phantom n’existait pas, et passer les tests d’intégration était vraiment une corvée pour les développeurs. En fait ce n’était pas tant un réquisitoire contre Selenium mais un aperçu des outils alternatifs qui ont émergé.

    C’est également une volonté de ma part de parler des outils JS, car avec l’émergence des frameworks RIA les développeurs d’interface utilisateur ont de plus en plus un profil front. Cela a amené pas mal de changement dans la culture dév. L’industrialisation se fait avec grunt/npm et non plus maven, on écrit en JavaScript ou assimilé. Il sera difficile de convaincre un développeur front d’écrire ses tests en Java.

  5. Publié par Sylvain, Il y a 4 années

    Avec Selenium, on peut aussi écrire les tests en Javascript (Java, Python, Ruby et C#). Il me semble qu’il existe un plug-in pour grunt. C’est sûr, il est moins « hype » que les autres.

    Je suis tout de même d’accord qu’avec les nouvelles technologies « MVC client » (AngularJS, backbone, etc), il vaut peut être mieux se tourner vers ces nouveaux outils (Zombie, Karma, etc).

  6. Publié par Antoine, Il y a 4 années

    Bonjour,

    Merci pour l’article,

    Il existe un outil open-source Cerberus en cours de développement, version 1.0 en préparation encapsulant en partie Selenium et permettant de faire dialoguer développeurs / QA / end-users.

    https://github.com/vertigo17/Cerberus

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *