Joel Alejandro Villarreal Bertoldi

Córdoba  ·  Febrero 2017

Front-end Unit Testing

WORKSHOP

¿QUÉ
SIGNIFICA
"TEST"?

"Un procedimiento cuyo objetivo es establecer la calidad, performance o confiabilidad de algo, especialmente antes de que sea conducido a un uso masivo."

¿Y UN TEST

UNITARIO?

"Un test unitario es una pieza automatizada de código que invoca a una unidad de trabajo en el sistema y luego chequea una única suposición acerca de su comportamiento"

"The Art of Unit Testing"
— Roy Osherove

"Una unidad de trabajo es un caso de uso lógico-funcional en el sistema que puede ser invocado por alguna interfaz pública. Puede abarcar un método, una clase o múltiples clases en conjunto para alcanzar un único objetivo verificable."

"The Art of Unit Testing"
— Roy Osherove

¿UNIDAD
DE TRABAJO?

 

¿Y LOS TESTS FUNCIONALES? ¿Y LOS DE INTEGRACIÓN? ¿Y LOS DE ACEPTACIÓN?

UNIT TEST

INTEGRATION TEST

 

 

 

FUNCTIONAL TEST

 

 

 

 

 

 

ACCEPTANCE TEST










UNIT TEST:

La unidad mínima de código independiente y propio que puede probarse.

UNIT TEST

INTEGRATION TEST

 

 

 

FUNCTIONAL TEST

 

 

 

 

 

 

ACCEPTANCE TEST










INTEGRATION TEST:

Deriva de los unit tests, combinando las unidades de código probadas y comprobando que dichas combinaciones sean válidas.

En Front-end, aquí se sitúan los tests contra el DOM.

UNIT TEST

INTEGRATION TEST

 

 

 

FUNCTIONAL TEST

 

 

 

 

 

 

ACCEPTANCE TEST










FUNCTIONAL TEST:

También conocido como black-box test, consiste en comprobar entradas y salidas de cada función del sistema, sin conocer cómo opera por dentro.

UNIT TEST

INTEGRATION TEST

 

 

 

FUNCTIONAL TEST

 

 

 

 

 

 

ACCEPTANCE TEST










ACCEPTANCE TEST:

En desarrollo ágil, son tests basados directamente en las user stories. Comprueban el cumplimiento de los requerimientos del software.

¿ POR QUÉ

   TESTEAR ?

Para tratar de que esto no pase.

Los tests dan confianza del código escrito.

Los tests ayudan a cubrir los casos de uso de un sistema.

Los tests ayudan a detectar defectos en el código.

Los tests son fáciles de crear y rápidos de ejecutar.

Los tests reducen el esfuerzo de documentación.

Los tests permiten experimentar modificaciones en el código, considerando sus side effects.

Testear es divertido. (*)

(*) Sujeto al estado de ánimo del developer.

Como siempre ocurre, hay múltiples herramientas para realizar este tipo de testing, especialmente en Front-End.

TEST DESCRIPTION FRAMEWORKS

CasperJS

Mocha

QUnit

Describen los tests.

Como siempre ocurre, hay múltiples herramientas para realizar este tipo de testing, especialmente en Front-End.

ASSERTION FRAMEWORKS

Jasmine

Assert

Chai

Describen las validaciones.

Vamos a trabajar con Mocha...
(como descriptor de tests en Karma*)

(*) Test runner

y con Chai.
(como assertion framework)

import { describe, it } from 'mocha';
import { expect } from 'chai';

import Stuff from './src/Stuff';

describe('Stuff', function() {
    it('should do something', function() {
        expect(new Stuff()).to.do('something');
    });
});

Descriptor del grupo de tests

Enunciado del test individual

Expectativa

¿Cómo es un test?

¿Cómo se escriben los tests unitarios?

Todo test debe responder

cinco preguntas esenciales.

(según un artículo de Eric Elliot) 

¿Qué estamos testeando?

¿Qué debería suceder?

¿Qué está retornando el test?

¿El test retorna lo que esperamos?

¿Cómo se puede reproducir el test?

VAMOS
PASO
A
PASO.

Es importante no empezar por el código.

Es mucho más fácil comenzar
escribiendo en lenguaje natural
lo que se desea testear.

http://bit.ly/FEUnitTesting-Ejercicio1


 function convertToFullYear(partialYear) {
   let year = Number(partialYear);
   const fullYear = new Date().getFullYear();
   if (year >= 69 && year <= 99) {
     year += (Math.floor(fullYear / 100) - 1) * 100;
   } else {
     year += Math.floor(fullYear / 100) * 100;
   }
   return year;
 }

¿Qué hace esta función?

convertPartialToFullYear(year)

 

Debería convertir un año de 2 dígitos en uno de 4.

Debería recibir un número válido, entero en `year`.

No debería funcionar con otro tipo de dato que no pueda ser una representación de un año.

import { describe, it } from 'mocha';
import { chai } from 'chai';

describe('convertPartialToFullYear', function() {
    it('should convert a 2-digit year into a 4-digit year');
    it('should accept a valid integer value as parameter');
    it('should not accept a NaN-like type');
});

Aquí describimos las partes a testear de la función convertPartialToFullYear. Nótese que no hemos definido ninguna función en it().

SINTAXIS DE UNA FRASE DE PRUEBA

it('should [not] <action> [condition]')

CONJUGACIÓN

NEGACIÓN

ACCIÓN

CONDICIÓN

Las pruebas se escriben como condicionales.

Pueden ser condiciones negativas.

Evalúan una
acción en
particular.

Pueden tener en
cuenta ciertas
circunstancias.

Tomemos la primera prueba como base.

import { describe, it } from 'mocha';
import { chai } from 'chai';

describe('convertPartialToFullYear', function() {
    it('should convert a 2-digit year into a 4-digit year');
    it('should accept a valid integer value as parameter');
    it('should not accept a NaN-like type');
});
import { describe, it } from 'mocha';
import { chai } from 'chai';

describe('convertPartialToFullYear', function() {
    beforeEach(function() {
        this.partial = '90';
    });
    it('should convert a 2-digit year into a 4-digit year', 
    function() {
        const actual = convertPartialToFullYear(this.partial);
        const expected = 1990;
        expect(actual).to.be.a(Number);
        expect(actual.length).to.equal(4);
        expect(actual).to.equal(expected);
    });
});

¿Qué tenemos aquí?

{

{

{

Un preinicializador.

Antes de cada prueba (de cada "it"), se ejecuta esta función.

import { describe, it } from 'mocha';
import { chai } from 'chai';

describe('convertPartialToFullYear', function() {
    beforeEach(function() {
        this.partial = '90';
    });
    it('should convert a 2-digit year into a 4-digit year', 
    function() {
        const actual = convertPartialToFullYear(this.partial);
        const expected = 1990;
        expect(actual).to.be.a(Number);
        expect(actual.length).to.equal(4);
        expect(actual).to.equal(expected);
    });
});

{

Un comportamiento.

Definimos el comportamiento y tomamos el valor actual de la operación.

Definimos el valor esperado.

Escribimos las expectativas.

import { describe, it } from 'mocha';
import { chai } from 'chai';

describe('convertPartialToFullYear', function() {
    beforeEach(function() {
        this.partial = '90';
    });
    it('should convert a 2-digit year into a 4-digit year', 
    function() {
        const actual = convertPartialToFullYear(this.partial);
        const expected = 1990;
        expect(actual).to.be.a(Number);
        expect(actual.length).to.equal(4);
        expect(actual).to.equal(expected);
    });
});

Toda operación que testeemos en nuestro código debe ser determinista.

A igual input, igual output

Por ende, el test se reproduce sabiendo sus condiciones de entrada y salida.

El preinicializador que vimos anteriormente es un tipo de hook provisto por Mocha.

before()
beforeEach()
after()
afterEach()

Se ejecuta una vez antes de la suite de test cases.

Se ejecuta una vez antes de cada test a correr.

Se ejecuta una vez después de la suite de test cases.

Se ejecuta una vez después de cada test a correr.

Hablemos de expectativas.

Una expectativa es un enunciado que describe el comportamiento esperado en un test case.

SINTAXIS DE UNA EXPECTATIVA

expect(<subject>).to.<predicate>[(value)]

SINTAXIS DE UNA ASEVERACIÓN (*)

assert(<expression>[, message])

(*) Otra forma de validar comportamientos.

expect(this.day).to.equal('Awesome!')
assert(this.day === 'Awesome!', 'Oh :(')
to be been is that
which and has have with
at of same

CONECTORES

expect(this.day).to.have.been.called()

PREDICADOS: true, false, ok, not

expect(this.story).to.be.true;
expect(this.euro).to.not.be.false;
=> this.story === true
=> !(this.euro === false)
expect(this.day).to.be.ok;
=> !!this.day

PREDICADOS: null, undefined, instanceof

expect(this.contract).to.be.null;
expect(this.feeling).to.not.be.undefined;
=> this.contract === null
=> !(this.euro === undefined)
expect(this).to.be.an.instanceof(Freedom)
=> this instanceof Freedom === true

http://chaijs.com/api/bdd/

EL RESTO DEL DICCIONARIO

npm install --save-dev chai-backbone

EXTRA-ESPECIAL PARA CHAI

Agrega predicados específicos para Backbone.

expect(model).to.trigger('change').when(callback)
expect('/my/way').to.route.to(Router, method[, {params}])
expect(view).to.call('method').when(callback)

Testear es una actividad
importante y aún subestimada.

(En todo el universo.)

GUIDELINES

Legibilidad

Mantenibilidad

Confiabilidad

GUIDELINES DE LEGIBILIDAD

No abusar de los preinicializadores

Testear sólo una cosa

Dar buenos nombres a los tests

Nada de magic strings o magic numbers

GUIDELINES DE MANTENIBILIDAD

Tests aislados y repetibles

Testear sólo API pública

No sobreespecificar los tests

State-based over Interaction-based

GUIDELINES DE CONFIABILIDAD

Nada de lógica o valores dinámicos

No mezclar con tests de integración

Usar siempre valores fijos (no new Date)

¡A TESTEAR!

¿Preguntas?

¡Gracias y buenas tardes!