(o casi todo)
(particularmente
para Front-End)
Joel A. Villarreal Bertoldi
Buenos Aires · Octubre 2016
¿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?
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?
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.
ESTE ES
NUESTRO
PROYECTO.
swissblade.js
Consta de:
tijera, lima, navaja,
abrelatas, sacacorchos, etc.
RESPONDAMOS LAS DOS
PRIMERAS PREGUNTAS.
Es importante no empezar por el código.
Es mucho más fácil comenzar
escribiendo en lenguaje natural
lo que se desea testear.
Tomemos el componente Scissor/Tijera.
Supongamos que existe un método:
SwissBlade.scissor.cut()
Que corta un papel.
SwissBlade.scissor.cut()
Debería dividir el papel en dos partes.
Debería cortar papel de hasta X espesor.
No debería poder cortar materiales duros.
import { describe, it } from 'mocha';
import { chai } from 'chai';
describe('Swissblade.scissor.cut', function() {
it('should cut paper in two pieces');
it('should cut paper of up-to a certain thickness');
it('should not cut any hard materials');
});
Aquí describimos las partes a testear de nuestro componente Scissor. 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.
VEAMOS AHORA LA
3º Y 4º PREGUNTA:
¿CUÁL ES EL RETORNO?
¿Y EL VALOR ESPERADO?
import { describe, it } from 'mocha';
import { chai } from 'chai';
describe('Swissblade.scissor.cut', function() {
it('should cut paper in two pieces');
it('should cut paper of up-to a certain thickness');
it('should not cut any hard materials');
});
Tomemos la primera prueba como base.
import { describe, it } from 'mocha';
import { chai } from 'chai';
describe('Swissblade.scissor.cut', function() {
beforeEach(function() {
this.scissor = new Swissblade.Scissor();
this.paper = new Paper();
});
it('should cut paper in two pieces', function() {
const actual = this.scissor.cut(this.paper);
const expected = 2;
expect(actual).to.be.an('Array');
expect(actual.length).to.equal(expected);
});
});
¿Qué tenemos aquí?
{
{
import { describe, it } from 'mocha';
import { chai } from 'chai';
describe('Swissblade.scissor.cut', function() {
beforeEach(function() {
this.scissor = new Swissblade.Scissor();
this.paper = new Paper();
});
it('should cut paper in two pieces', function() {
const actual = this.scissor.cut(this.paper);
const expected = 2;
expect(actual).to.be.an('Array');
expect(actual.length).to.equal(expected);
});
});
{
Un preinicializador.
Antes de cada prueba (de cada "it"), se ejecuta esta función.
import { describe, it } from 'mocha';
import { chai } from 'chai';
describe('Swissblade.scissor.cut', function() {
beforeEach(function() {
this.scissor = new Swissblade.Scissor();
this.paper = new Paper();
});
it('should cut paper in two pieces', function() {
const actual = this.scissor.cut(this.paper);
const expected = 2;
expect(actual).to.be.an('Array');
expect(actual.length).to.equal(expected);
});
});
{
Un comportamiento.
Definimos el comportamiento y tomamos el valor actual de la operación.
Definimos el valor esperado.
Escribimos las expectativas.
¿Y LA ÚLTIMA?
¿CÓMO SE
REPRODUCE EL TEST?
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.)
REGLAS
Legibilidad
Mantenibilidad
Confiabilidad
REGLAS 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
REGLAS DE MANTENIBILIDAD
Tests aislados y repetibles
Testear sólo API pública
No sobreespecificar los tests
State-based over Interaction-based
REGLAS DE CONFIABILIDAD
Nada de lógica o valores dinámicos
No mezclar con tests de integración
Usar siempre valores fijos (no new Date)
DEMOS/EJEMPLOS/REAL LIFE
¿Preguntas?
¡Gracias y buenas tardes!