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!