>

Tests en el FrontEnd

Elena Torro     Colaboraciones    23/12/2016


Tests en el FrontEnd

Introducción

La idea principal de este post es mostrar ejemplos de las herramientas utilizadas a día de hoy a la hora de testear el frontend de una aplicación web. He decidido enfocarlo desde un punto de vista general, aunque los ejemplos sean actuales. Esto se debe a que en el desarrollo web las cosas están avanzando muy rápido, y es posible que dentro de unos meses las tecnologías utilizadas en los ejemplos hayan quedado desfasadas.

Antes de continuar, quiero puntualizar una cosa: personalmente, creo que la práctica supera a la teoría a la hora de testear, y que los tests se han de adaptar a cada aplicación, y no al revés. Por esto, el post es una recopilación teórica de los tipos de tests y de herramientas actuales que son de utilidad para probar aplicaciones web en general, no son los diez mandamientos del testing :).

Frameworks, librerías y viceversa

Para comenzar, he incluido un breve análisis de las respuestas de un formulario de Google Docs sobre testing que mucha gente me ha ayudado a rellenar, a todos ellos: muchas gracias :)

Hay muchas librerías, herramientas y frameworks con los que hacer cualquier cosa de muchas maneras. Por esto, quería conocer cuáles eran las más utilizadas por los desarrolladores a día de hoy, y me gustaría compartir los resultados de un cuestionario muy cortito pero del que obtenemos una perspectiva del estado actual. El cuestionario lo publiqué en Twitter y lo envié unos grupos de FrontEnd en los que estoy.

En total, han participado hasta la fecha 36 personas. Éste es un análisis de los resultados:


  • De los participantes de la encuesta, la mayoría usa React, seguido en orden de uso por Angular 1.x, Angular 2, EmberJS, Polymer y Backbone.

  • La gran mayoría escribe tests, aunque el 11,5% no lo hace casi nunca y el 7,7% nunca.

  • Las librerías más utilizadas son Mocha, Jasmine, Selenium, PhantomJS y Karma.

  • Un 65,4% usa integración continua.

  • Un 15,4% prueba su CSS

  • Un 26,9% prueba la accesibilidad (es decir, tienden más a probar la accesibilidad que el CSS)

Añadí al formulario una pregunta de libre respuesta, en la que pedía que me dijeran qué era lo que más difícil les costaba a la hora de testear. La respuesta que más aparece es accesibilidad. Le siguen la interacción del usuario y la integración con los datos.

Qué probar, cómo probar y con qué probar

Ésas son las tres preguntas que me planteo a la hora de comenzar a escribir tests. Para empezar y ordenar un poco las ideas, he clasificado esta sección por tipos de tests, con el objetivo de aportar una descripción de cada tipo junto con distintas herramientas y técnicas. Para ello, voy a mostrar ejemplos que se pueden encontrar en proyectos Open Source de empresas y personas más o menos conocidas.

Tests unitarios

El propósito es probar funcionalidades individuales, las cuales funcionan independientemente del resto del software de la aplicación: se podría decir que son pruebas de ‘caja-blanca’. Realizar tests unitarios ayuda a construir una arquitectura más modular. Veamos el siguiente ejemplo:

Este test prueba que el componente ‘loading-indicator’ se ha renderizado correctamente y se encuentra en el DOM:



import { test, moduleForComponent } from 'ember-qunit';

moduleForComponent('loading-indicator', {
  unit: true
});

test('it renders', function (assert) {
  const component = this.subject({
   center: true
  });

  this.render();
  assert.ok(component.$('span').hasClass('loading-indicator'), 'component has loading indicator class');
  assert.ok(component.$().hasClass('loading-container'), 'indicator gets parent class if centered flag is given');
});

Tests de integración

Es hora de “integrar” las distintas piezas de código, desde el código de tu propia aplicación hasta el de las librerías y dependencias que has importado para comprobar que todo junto marcha bien.

Este ejemplo está sacado del administrador de la plataforma de blogging Ghost, que está disponible en GitHub.


import {expect} from 'chai';
import {describe, it} from 'mocha';
import {setupComponentTest} from 'ember-mocha';
import hbs from 'htmlbars-inline-precompile';
import Service from 'ember-service';
import {A as emberA} from 'ember-array/utils';

let notificationsStub = Service.extend({
    notifications: emberA()
});

describe('Integration: Component: gh-notifications', function () {
    setupComponentTest('gh-notifications', {
        integration: true
    });

    it('renders', function () {
        this.render(hbs`{{gh-notifications}}`);
        expect(this.$('.gh-notifications').length).to.equal(1);

        expect(this.$('.gh-notifications').children().length).to.equal(2);

        this.set('notifications.notifications', emberA());
        expect(this.$('.gh-notifications').children().length).to.equal(0);
    });
});

Al igual que TravisCI, el panel de administrador Ghost está hecho EmberJS. Sin embargo, en lugar de utilizar la suite de testing que viene por defecto con QUnit, utilizan Mocha. En este caso, está probando el componente gh-notification. Prueba que el componente gh-notifications contiene varias notificaciones. Es un test muy sencillo, pero recomiendo que vayáis al repositorio de GitHub ya que tiene bastantes ejemplos de tests.

Tests de aceptación

Para probar que todo funcionará de la manera esperada cuando el usuario interaccione con la aplicación se realizan los tests de aceptación. La idea de los tests de aceptación es que resuelven casos de uso. Es muy común utilizar herramientas automáticas, de entre las cuales destacan PhantomJS, Selenium o Nightmare. Este tipo de tests molan mucho, porque vas viendo como si un usuario ‘fantasma’ fuera interaccionando con tu aplicación.



var config = {
 url: 'http://fourword.fourkitchens.com/article/simulate-user-actions-casperjs',
};

config.form = {
 "name": "Chris Ruppel",
 "email": "me@example.com",
 "project-title": "CasperJS Test Project",
 "project-desc": "CasperJS Test Project Description"
};

casper.test.begin('Testing navigation and forms', 4, function suite(test) {
 test.comment('Time Loading ' + config.url + '...');
 casper.then(function () {
 test.assertUrlMatch(/contact/, 'New location is ' + this.getCurrentUrl());
 casper.fill('#contact', config.form, false);
 });

 casper.then(function () {
   test.assertEvalEquals(function () {
   return $('#contact input[name="name"]').val();
 }, config.form.name, 'The name was filled out properly.');

 test.assertEvalEquals(function () {
   return $('#contact input[name="email"]').val();
     }, config.form.email, 'The email was filled out properly.');
   });
 
casper.run(function () {
  test.done();
 });
});

Este código prueba que primero, se accede a la página de contacto y que posteriormente se rellena el formulario contacto correctamente.

Tests de regresión

Los tests de regresión prueban que la aplicación sigue funcionando como cabe de esperar cuando se hacen cambios, y que se comportan de la manera esperada de cara al usuario. Sí, tienen mucho en común con los tests de aceptación, a diferencia que los tests de aceptación suelen cubrir como he dicho antes casos de uso específicos, que cumplen unos requerimientos de diseño, o negocio, etc. Pero recuerda que esto es sólo una definición teórica, en la práctica estos límites entre “qué es qué” pueden cambiar.



describe('WebdriverCSS compares images and exposes information about CSS regression', function() {
// ... test configuration omitted

 describe('should take a screenshot of same area without any changes in it', function(done) {
 var resultObject;

 before(function(done) {
   this.browser
     .webdrivercss('comparisonTest', capturingData, function(err, res) {
       should.not.exist(err);
       resultObject = res[capturingData.name][0];
     })
    .call(done);
 });

 it('should exist an image (*.baseline.png) in the default image folder', function(done) {
    fs.exists(resultObject.baselinePath, function(exists) {
    exists.should.equal(true);
    done();
   });
 });

 it('should return a proper result object', function() {
   resultObject.misMatchPercentage.should.equal(0);
   resultObject.isExactSameImage.should.equal(true);
   resultObject.isSameDimensions.should.equal(true);
   resultObject.isWithinMisMatchTolerance.should.equal(true);
  });
 });
});

Este test de regresión comprueba las dimensiones de una imagen. Aquí puedes ver otro ejemplo que utiliza Jasmine.

Tests de CSS

Los tests de CSS están estrechamente relacionados con los tests de regresión, es decir: probar que todo sigue mostrándose e interactuando con el usuario tal y como debería. Pero esto no quita que puedas probar el CSS de un componente en un test unitario o en un test de integración.



casper.test.begin( 'Coffee machine visual tests', function ( test ) {
  phantomcss.init( {
    rebase: casper.cli.get( "rebase" ),
    casper: casper,
    libraryRoot: fs.absolute( fs.workingDirectory + '' ),
    screenshotRoot: fs.absolute( fs.workingDirectory + '/screenshots' ),
    failedComparisonsRoot: fs.absolute( fs.workingDirectory + '/demo/failures' ),
    addLabelToFailedImage: false,
  });

  casper.start( 'http://localhost:8080' );

  casper.viewport( 1024, 768 );

  casper.then( function () {
    phantomcss.screenshot( '#coffee-machine-wrapper', 'open coffee machine button' );
  });

  casper.then( function () {
    casper.click( '#coffee-machine-button' );
    casper.waitForSelector( '#myModal:not([style*="display: none"])', function success() {
      phantomcss.screenshot( '#myModal', 'coffee machine dialog' );
        }, function timeout() {
          casper.test.fail( 'Should see coffee machine' );
        });
      });

      casper.then(function now_check_the_screenshots() { phantomcss.compareAll();});
      casper.run(function () { console.log( '\nTHE END.' ); casper.test.done(); });
});

En este caso, el test compara varias capturas de pantalla realizadas con PhantomCSS mientras utiliza Casper para interaccionar con la página. Otra herramienta muy útil que encontré fue BackstopJS.

Tests de accesibilidad

En general, lo que los desarrolladores FrontEnd creen que es más difícil testear es la accesibilidad, según los resultados del cuestionario. Esto es en realidad una afirmación muy ambigua ya que, ¿quieren decir que lo más difícil es probar problemas de accesibilidad, o escribir tests / automatizar tests de accesibilidad? Para esto último, ya existen librerías que podemos incluir en nuestra suite de testing.

El problema que existe es, desde mi punto de vista, es que no sabemos qué probar para asegurar que nuestra página es accesible. La solución es, sin duda, leer mucho las especificaciones y hacer experimentos. Por ejemplo, os dejo el link a mi post anterior en el blog de betabeers sobre accesibilidad y menús. Para probar la accesibilidad de un menú tengo que tener claro qué requerimientos ha de cumpir el menú para ser accesible.

Lo que yo solía hacer hasta ahora era probar con validadores de accesibilidad como extensiones y plugins de distintos navegadores, como por ejemplo, aXe. Sin embargo, creo que es mucho más útil incluir herramientas dentro del resto de tu suite de testing. Esto permite añadir tests de accesibilidad unitarios para probar funcionalidades concretas, en lugar de probar solamente la accesibilidad de una pantalla o de una web completa, como suelen hacer estas herramientas.


test.cb('test local input generates a report if callback is third param', function (t) {
    t.plan(3);

    a11y('fixture.html', null, function (err, reports) {
        t.ifError(err);
        var ariaReports = auditsWithHeader(reports, 'ARIA state and property values must be valid');
        t.is(ariaReports.length, 1);
        t.is(ariaReports[0] && ariaReports[0].result, 'FAIL');
        t.end();
    });
});

test.cb('test local input generates a report requiring a delay', function (t) {
    t.plan(3);

    a11y('fixture.html', {delay: 5}, function (err, reports) {
        t.ifError(err);
        var delayReports = auditsWithHeader(reports, 'role=main should only appear on significant elements');
        t.is(delayReports.length, 1);
        t.is(delayReports[0] && delayReports[0].result, 'FAIL');
        t.end();
    });
});

En este test se está probando que los atributos ARIA que contiene la web son correctos, y también la presencia del atributo role=main.

Seguro que has echado de menos muchas herramientas que no he mencionado en el artículo, como Jest o Protractor. Si hiciera un artículo mencionándolas todas, ¡nadie lo leería porque sería demasiado largo! Por eso te animo a que comentes y envíes algún ejemplo interesante de tests, así como alguna herramienta que te haya sido de gran utilidad y por qué.

Referencias y otros links de interés


Sobre el autor