DÍA 7 / 2018

Testing en el front

En un mundo en el que está todo continuamente avanzando resulta muy importante crear cada vez más, proyectos robustos, sostenibles y mantenibles en el tiempo. Por eso, el concepto del testing deber ser algo inherente a cualquier desarrollador igual que a día de hoy lo son palabras como "responsive" o "crossbrowsing".


Introducción

Sabemos de sobra que todo aquello que desarrollamos debe funcionar correctamente independiente de la resolución o sistema operativo que esté utilizando el usuario final. Por ello, resulta algo cada vez más importante que aquello que desarrollamos no tenga errores o si los tiene, sean errores controlados.

Vamos a comenzar tratando este asunto tan importante desde lo más básico:

¿Qué es un test?

Un test es un bloque de código que se encarga de testear que una parte de nuestra aplicación funciona como se espera. En otras palabras, que aquello que hemos desarrollado siguiendo las especificaciones marcadas en el proyecto funciona tal y como se nos ha requerido.

A lo mejor este escenario te suena, pero es muy común en los equipos de trabajo hacer la revisión de código, o Code Reviews, de nuestras Pull requests, básicamente uno o varios compañeros del equipo revisan nuestro código y validan que funciona según lo esperado.

O a través de un agente externo al código como puede ser una persona del departamento de QA, dentro de la compañía, más encargado de la parte de negocio. Alguien que no revisa el código pero si la funcionalidad esperada de aquello que se ha desarrollado.

Por lo tanto, el hecho de crear tests automatizados en nuestra aplicación, cumple esencialmente dos requisitos:

  • Comprobar que nuestra aplicación funciona como se espera, siguiendo los requisitos definidos en el proyecto y sin errores inesperados.
  • Hacer esa comprobación automáticamente. Según los casos que hemos comentado anteriormente esa comprobación se hace manual, pero tenemos infinidad de herramientas que nos permiten hacer ese mismo testeo de forma automática. Con la simple ejecución de un comando en consola, se podrían ejecutar todos los tests que hayamos definido.

Tipos de tests

Unitarios

Son aquellos que realizan la comprobación de una funcionalidad. Se podría decir que testean métodos que se encargan de ejecutar una tarea. De forma que recibiendo unos parámetros devuelve un valor esperado.

Ejemplo: Testear que un método que suma dos elementos que al recibirlos como parámetros, los suma adecuadamente.

Integración

Son aquellos que comprueban la interacción de elementos en nuestra aplicación. Se podría decir que sobre una arquitectura basada en componentes comprueba que cierto componente funciona según lo esperado, conviviendo con otros componentes.

Ejemplo: Comprobar que al acceder a cierta ruta se renderiza el formulario adecuado.

Aceptación (EndToEnd)

En este caso, lo que se pretende testear son los flujos de nuestro aplicación según el punto vista del usuario final. Ciclos de la aplicación completos, de principio a fin.

Ejemplo: Cómo es el flujo de un usuario que se registra en nuestra plataforma.

Contexto en testing

Sabiendo esto, debemos tener en cuenta que nuestra aplicación debe funcionar como se espera si definimos los tests según vamos desarrollando nuestra aplicación. De forma que:

  1. Testeamos los métodos más importantes para el buen funcionamiento de nuestra lógica de negocio o puntos clave.
  2. Testeamos la interacción de componentes.
  3. Testeamos los flujos de nuestra aplicación.

Las herramientas de tests nos permiten primero describir la situación que queremos testear, y según los métodos denominados asserts comprobar que pasa lo que esperamos en un método específico.

Además, comprobamos la interacción entre elementos dentro de nuestra aplicación. Como puede ser, la comprobación de que al hacer clic en un botón se llama a un método. Dicho método, siguiendo la lógica que estamos aplicando, ya debería tener un test unitario que compruebe su funcionamiento.

La cobertura de tests, es importante tenerla en cuenta pero tampoco hay que volverse loco con los porcentajes, al final teniendo comprobado todo aquello que aplica lógica sobre nuestra aplicación es suficiente.

Herramientas para hacer testing

Lo cierto es que al empezar a navegar en internet sobre este tema se encuentran infinidad de herramientas para realizar tests en nuestras aplicaciones.

Si eres un novato en este tema y quieres empezar de una forma sencilla a testear tus aplicaciones, desde mi opinión personal, te aconsejo: Jest y / o Jasmine. Verás que su configuración es muy sencilla, vas a poder empezar a crear tests de una manera muy rápida y vas a tener en poco tiempo una cobertura más o menos amplia de tus aplicaciones creando tests unitarios y test de integración.

Ejemplo de test unitario con Jest:

Imaginemos un método muy sencillo que ejecuta una tarea. En este caso, sumar dos elementos que le llegan como parámetros:

function suma(a, b) {
  return a + b;
}
module.exports = suma;

El test que debemos crear para comprobar que este método hace lo que se espera es el siguiente:

const suma = require('./suma');
test('sumar 1 + 2 es igual a 3', () => {
  expect(suma(1, 2)).toBe(3);
});

En este caso el test es tan sencillo como el método. Pues lo único que hacer es comprobar que aquello que ejecuta en el expect retorna el valor esperado en el toBe. Si quieres indagar en todo aquello que puedes testear con Jest aquí tienes más información.

Para realizar un test de integración hemos hecho lo siguiente:

it('displays message when data is loading', () => {
  let data = { loading: true }
  let component = renderer.create(<Widgets data={data} />)
  let tree = component.toJSON()
  expect(content(tree)).toContain('Loading')
})

Con este test lo que comprobamos es que al renderizar nuestro componente <widgets>, este aparezca en el DOM y al pasarle en el data la propiedad loading como true, éste contenga la palabra Loading en su contenido.

Para realizar un test de aceptación en tu aplicación es normal depender de otra herramienta especializada solo para poder comprobar que el flujo que vas a testear es el adecuado:

import nightmare from 'nightmare'

describe('When visiting the homepage', function () {
  test('it welcomes the user', async function () {
    let page = nightmare().goto('http://localhost:3000')
    let text = await page.evaluate(() => document.body.textContent).end()
    expect(text).toContain('Welcome to React')
  })
})

Este ejemplo, precisa de nigthmare para poder hacer la comprobación de que al visitar cierta página aparece un mensaje, algo que sin esta dependencia no podríamos hacer. Lo más interesante de los tests de aceptación es que puedes encadenar tantas acciones como necesites testear. Por ejemplo, además de comprobar lo que hemos incluido en el test de ejemplo, podemos testear también que el usuario haga clic en un lugar y pase otra acción.

Por último, aunque este artículo se centra en el test dentro del mundo front, no podía dejar de lado dicho testeo en el repositorio. Para lo que también tenemos diversas herramientas que nos ayudan a ejecutar nuestros tests cuando se sube una nueva versión del código al repositorio. Como puede ser: Travis o Jenkins. De forma que si ejecutamos nuestros tests y alguno de ellos falla antes de subir una nueva versión del proyecto, no se sube ese código a producción y evitamos cualquier error.

Conclusiones

Por tanto, lo que propongo con este artículo es hacer del testeo de nuestros desarrollos un elemento más, igual que ya lo son otros asuntos relacionados con nuestro sector. Tratar de normalizar el test de nuestras aplicaciones y acercar este asunto a cualquier desarrollador.

¿Son necesarios los tests? ¡Por supuesto! Es la única forma de prevenir automáticamente cualquier error inesperado, ya que al crear el test estamos teniendo controlado lo que debe pasar en función de lo que indiquemos al test qué debe pasar.

Además, de forma indirecta, al crear los distintos casos de uso que pueden afectar a la funcionalidad que estamos testeando estamos creando la documentación sobre nuestro código.