Archives mensuelles : octobre 2014

ES6 : quelques nouveautés de la prochaine version de JavaScript

JavaScript est basé sur la norme EcmaScript. La version 6, appelée Harmony, va apporter beaucoup de nouveautés qui vont améliorer et combler certains manques du langage.
Voici quelques améliorations que je trouve très intéressantes :

let

Let est le nouveau var. Il permet de déclarer une nouvelle variable mais avec un scope (portée) local contrairement à var.

Ainsi avec var, la portée est soit globale soit liée à la fonction dans laquelle celle-ci est définie. Let permet d’avoir une granularité plus fine (for, if, …) comme il l’est dans d’autres langages.

var t = 1;
if (true) {
  let t = 3;
  for (; t <= 5; t++) {
  	console.log(t);
  }
}
console.log(t);

// 3
// 4
// 5
// 1

Le lien vers la documentation de let sur MDN et un article complet sur le sujet : Variables and scoping in ECMAScript 6

Arrow functions

Les Arrow function est une nouvelle façon de définir des fonctions, un peu comme les expressions lambda en C#. Cela peut s’avèrer pratique dans le cas de définition de callback par exemple.

Une arrow function se définit via =>. La déclaration des paramètres se fait avant la flèche. Si il y a plusieurs paramètres, on entourera les paramètres par des parenthèses. Si l’expression est sur une ligne, celle-ci sera retournée. Si elle est sur plusieurs lignes, celle-ci doit être entourée d’accolades.

Voici un exemple d’un map avec une fonction qui met les valeurs au carré.

var myValues = [1,2,3,4,5];

var squared = myValues.map(x => x*x);
console.log(squared);  //-> [1, 4, 9, 16, 25]

//définition d'un function qui permet de déterminer si n nombre est pair
var even = (x) => x % 2 == 0;
var evenValues = myValues.filter(even);
console.log(evenValues);  //-> [2, 4]

var complex = (a, b) => {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

Le lien vers la documentation des arrow functions sur MDN.

Rest Parameters

Les rest parameters permettent de définir une fonction qui prend un  nombre infini de paramètres sous la forme d’un tableau.

On définit notre dernier paramètre comme étant un rest parameter en ajoutant … devant le nom du paramètre. Nous ne sommes plus obligé de passer par arguments. L’avantage étant ici que notre paramèter est nommé et qu’il s’agit d’un véritable array et qu’on peut donc lui appliquer ses méthodes spécifiques (reduce, sort, map, …).

On peut appeler notre méthode de 2 façons :

  • en passant la liste sous forme de paramètres à la suite les uns des autres
  • en passant la liste sous forme d’un tableau. Dans ce cas, il faut le préfixer par …

Voici un exemple (issue de la page MDN concernant les Rest Parameters et compléter avec l’appel sous forme de tableau)

function multiply(multiplier, ...theArgs) {
  return theArgs.map(function (element) {
    return multiplier * element;
  });
}

var arr = multiply(2, 1, 2, 3); 
console.log(arr); // [2, 4, 6]

var arr = multiply(2, ...[1, 2, 3]); 
console.log(arr); // [2, 4, 6]

Cela ressemble aux params en C#.

Default Parameters

ES6 va permettre de donner des valeurs par défaut aux paramètres de vos fonctions. Cela se fait comme dans d’autres langagues de la façon suivante :

function sayMsg(msg='This is a default message.') {
	console.log(msg);
}
sayMsg();
sayMsg('This is a different message!');

Le lien vers la documentation des default parameters sur MDN.

Array comprehension

Array comprehension est une nouvelle syntaxe pour créer des tableaux à parir de tableaux existant.

Pour cela on va utiliser la syntaxe suivante :

var square = [for (i of [ 1, 2, 3 ]) i*i ]; 
// [ 1, 4, 9 ]

var abc = [ 'A', 'B', 'C' ];
var lowChar = [for (letters of abc) letters.toLowerCase()];
// [ 'a', 'b', 'c' ]

//On peut rajouter des conditions
[for (i of [1, 2, 3, 4]) if (i > 3) i];
// [ 1, 2 ] 

//utiliser plusieurs tableaux
var cross = [for (i of [1, 2, 3]) for (j of [a, b, c]) i+j];
// [ '1a', '1b', '1c', '2a', '2b', '2c', '3a', '3b', '3c']

Le lien vers la documentation des Array comprehension sur MDN.

Generators

Les generators sont des itérateurs spéciaux qui permettent de récuperer la valeur suivante que lors de l’appel à next.

Il introduit le mot clé yield qui permet de définir la valeur retournée. Lors du permier appel à la fonction Generator, on récupère l’itérateur. La récupération d’une nouvelle valeur se fait via next(). Une exception StopIteration est lancée si il n’y pas de nouvelle valeur. Lors d’un appel à next, la fonction continuera son exécution à partir du dernier yield atteint.

Un Exemple tiré d’un article sur les Iterators et Generators sur MDN.

function simpleGenerator(){
  yield "first";
  yield "second";
  yield "third";
  for (var i = 0; i > 3; i++)
    yield i;
}

var g = simpleGenerator();
print(g.next()); // prints "first"
print(g.next()); // prints "second"
print(g.next()); // prints "third"
print(g.next()); // prints 0
print(g.next()); // prints 1
print(g.next()); // prints 2
print(g.next()); // StopIteration is thrown

Template Strings

Permet de définir des templates sous la forme de chaîne de caractères sans passer par une librairie externe (mustache, jsrender, …)

La définition d’un string template se fait via un ` (back tick). Les variables sont définies via ${ x } (x étant la variable).
Les templates string peuvent être définis sur plusieurs lignes (sans devoir faire de concaténation comme avec les strings).

let person = {name: 'Julien Roy'};
let tpl = `My name is ${person.name}.`;

console.log(tpl); //My name is Julien Roy

Autres nouveautés

Harmony apporte également d’autres nouveautés:

  • les classes
  • les modules
  • Mutation observers

Je parlerai dans un autre article de ces nouveautés très attendues.

Edit : le deuxième article sur les nouveautés de ES6.

Utilisation

Bien entendu, toutes ses nouveautés ne sont pas encore toutes intégrées dans les navigateurs, sachant que les spécifications ne sont pas encore figées et peuvent être amenées à évoluer. Il existe tout de même des outils qui permettent d’utiliser ces fonctionnalités comme des polyfills ou des transpileurs (compilation de code ES6 en ES5) comme Traceur ou 6to5. (les liens ci dessous ..)

Conclusion

ES6 apporte pas mal de nouveautés attendues par les développeurs qui vont permettre à JavaScript de combler certains manques et améliorer grandement son écriture pour en faire un langage « first class ». Pour information, la version 2.0 d’Angular est écrite en EcmaScript 6.

Références

Protractor : Améliorer vos tests E2E avec le pattern Page Objects

J’ai déjà parlé dans des articles précédents de Protractor (ici et ), l’outil qui permet de faire des tests d’interface, end-to-end (E2E).

Je vais vous présenter ici Page Objects, un design pattern afin de faciliter le développement de vos tests. Page Objects permet :

  • d’avoir un code plus lisible
  • d’éviter la duplication de code
  • d’éviter d’avoir un couplage fort entre votre test et la page à tester
  • de faciliter la maintenance de vos tests

Une page object est une classe qui va servir d’interface entre la page à tester et vos tests. Ainsi, le test n’a pas à interagir avec votre interface mais passe par la classe « Page Object ». Ainsi, si votre interface change, seulement le code de votre page objet sera à modifier et non le test, facilitant ainsi la maintenance. La page object va exposer des méthodes fonctionnelles qui vont rendre plus lisible vos tests.

Exemple

Voici un exemple simple concernant une page de login.

Voici le test sans utilisation de page objects.

describe('Login', function () {

  var ptor, loginInput, passInput, submitBtn, error;

  beforeEach(function () {
    ptor = protractor.getInstance();
    loginInput = by.model('username');
    passInput = by.model('password');
    submitBtn = by.css('[type=submit]');
    error = by.binding('error');
  });

  it('should display error if login password are wrong', function () {
    element(loginInput).sendKeys('wrongUser');
    element(passInput).sendKeys('pass123');
    element(submitBtn).click();

    browser.waitForAngular();

    expect(ptor.getCurrentUrl()).not.toMatch(/\/home/);
    expect(element(error).getText()).not.toEqual('');
  });

  it('should redirect to home page is login/pass are correct',  function () {
    element(loginInput).sendKeys('admin');
    element(passInput).sendKeys('123456');
    element(submitBtn).click();

    browser.waitForAngular();

    expect(ptor.getCurrentUrl()).toMatch(/\/home/);;
  });
});

Voici comment améliorer nos tests avec Page Object. Dans le cas de protractor, les tests sont exécutés via node js. Nous allons créer un module node qui pourra ainsi être chargés dans nos tests.

La page Object de la page de login :

'use strict';

var LoginPage = function () {
  browser.get('http://mySiteUrl');
};

LoginPage.prototype = Object.create({}, {
    loginText: { get: function () { return element(by.model('username')); }},
    passwordText: { get: function () { return element(by.model('password')); }},
    loginButton: { get: function () { return element(by.css('input[type="submit"]')); }},
    error = { get: function () { return element(by.binding('error')).getText(); }},
    login: { value: function (login, password) {
    	this.loginText.sendKeys(login);
    	this.passwordText.sendKeys(password);
    	this.loginButton.click();

        browser.waitForAngular();
  }}
});

module.exports = LoginPage;

Le test devient :

//On charge le module
var LoginPage = require('../pages/login.page.js');

describe('Login', function () {
var page, ptor;

  beforeEach(function () {
    //avant chaque test on on récupère la page
    page = new LoginPage();
    ptor = protractor.getInstance();
  });

  it('should display error if login password are wrong', function () {
     page.login('wrongUser', 'pass');

     expect(ptor.getCurrentUrl()).not.toMatch(/\/home/);
     expect(page.error).not.toEqual('');
  });

  it('should redirect to project home page is login/pass are correct',   function () {
    page.login('julien', 'azerty123');

    expect(ptor.getCurrentUrl()).toMatch(/\/home/);
  });
});

On peut voir que le test est beaucoup plus lisible et maintenable.

De plus, il devient facile de réutiliser notre code. En effet, si on doit tester des pages ou l’on doit être identifié, on peut facilement réutiliser la page objects de login afin de se connecter avant chaque test.

J’ai mis à jour les tests E2E de mon application de volley. Le code est disponible sur github.

Références

Bon tests 😉