Artículo original escrito por: Ondrej Polesny
Artículo original: How to Start Unit Testing Your JavaScript Code
Traducido y adaptado por: Sil Zubikarai

Todos sabemos que debemos escribir pruebas unitarias. Pero, es difícil saber donde comenzar y cuanto tiempo debemos dedicar a probar comparado a la implementación actual. Entonces ¿dónde comenzar? ¿Y se trata solo de probar el código o las pruebas unitarias tienen otros beneficios?

En este artículo, vamos a explicar los diferentes tipos de pruebas, y que beneficios las pruebas unitarias traen a los equipos de desarrolladores. Mostraré Jest, un marco de pruebas de JavaScript.

Diferentes tipos de pruebas.

Antes de sumergirnos en los detalles de las pruebas unitarias. Quiero  hacer un recorrido rápido a los diferentes tipos de pruebas. A menudo hay cierta confusión  a su alrededor y no estoy sorprendido. A menudo la línea entre ellos es bastante delgada.

Pruebas Unitarias

Las pruebas unitarias solo prueban una sola parte de tu implementación. Una unidad. Sin dependencias, ni integraciones, ni detalles del framework. Son como un método que regresa un link en un lenguaje específico:

export function getAboutUsLink(language){
  switch (language.toLowerCase()){
    case englishCode.toLowerCase():
      return '/about-us';
    case spanishCode.toLowerCase():
      return '/acerca-de';
  }
  return '';
}

Pruebas de integración

En cierto punto, tu código se comunica con una base de datos, el sistema de archivos u otro tercero. Incluso podría ser otro módulo en tu aplicación.

Esa pieza de implementación debe ser probada por las pruebas de integrador. Por lo general, tienen una configuración más complicada que implica preparar entornos de prueba, inicializando dependencias, etc.

Pruebas Funcionales

Las pruebas unitarias y las pruebas de integración te dan la confianza que tu aplicación trabaja. Las pruebas Funcionales ven a la app  desde el punto de vista del usuario y prueba que el sistema trabaja como es esperado.

presentation

En el diagrama de arriba, viste que las pruebas unitarias forman la gran base del conjunto de pruebas de aplicación. Por lo general, son pequeños, hay muchos de ellos, y sé ejecutados automáticamente.

Entonces ahora vamos a ver las pruebas unitarias en mayor detalle.

¿Por qué te debes molestarte en escribir pruebas unitarias?

Siempre que pregunto a desarrolladores si escriben prueban unitarias para su aplicación, ellos siempre me dicen:  "No tengo tiempo para ellas" o "No las necesito, yo sé cómo trabaja"

Entonces sonrío educadamente y les digo lo que quiero decirles. La pruebas unitarias no se tratan solo de pruebas. También te ayudan de otras maneras, para que puedas:

Confía en que tu código funciona. ¿Cuándo fue la última vez que cometiste un cambio en el código,  fallo la compilación, y la mitad de tu aplicación dejo de trabajar? La mía fue la semana pasada.

Pero eso todavía está bien. El verdadero problema es cuando la compilación se realiza correctamente, el cambio es llevado, y tu aplicación empieza a ser inestable.

Cuando eso pasa, empiezas a perder confianza en tu código y eventualmente solo pedirás que tu aplicación funcione. Las pruebas unitarias te ayudarán a descubrir errores mucho antes y a ganar confianza.

Tomar mejores decisiones de arquitectónicas. El código cambia, pero algunas decisiones sobre la plataforma, módulos, estructura, y otros cambios se necesitan hacer en tempranas etapas del proyecto.

Cuando empiezas a pensar acerca de las pruebas unitarias justo al inicio, le ayudara a estructurar mejor su código y lograr una separación adecuada de las preocupaciones. No tendrá la tentación de asignar múltiples responsabilidades a un solo bloques de código único, ya que serian una pesadilla para la prueba unitaria.

Identifique la funcionalidad antes de codificar. Escribe la firma del método y comienza a implementarlo de inmediato. Ah, pero ¿qué debería suceder en caso de que un parámetro sea nulo? ¿Qué sucede si su valor está fuera del rango esperado o contiene demasiados caracteres? ¿Lanza una excepción o devuelve null?

Las pruebas unitarias ayudarán a descubrir todos los casos. Mira las preguntas otra vez y encontrarás exactamente que definirás tus casos de pruebas unitarias.

Estoy seguro de que hay muchos otros beneficios de escribir pruebas unitaria. Estos solo algunos de los que recuerdo por mi experiencia. Estos los he aprendido de la manera difícil.

Como escribir tu primera prueba unitaria de JavaScript

Pero vamos a regresar a JavaScript. Vamos a empezar con Jest, que es un marco de pruebas de JavaScript. Es una herramienta que permite realizar  pruebas unitarias automáticas, proporciona  cobertura de código, y nos permite simular fácilmente objetos. Jest también tiene una extensión para Visual Studio Code disponible aquí.

También hay otros marcos, si tú estás interesado, tú puedes revisarlos en este artículo.

npm i jest --save-dev

Vamos a usar el método mencionado anteriormente getAboutUsLink como una implementación que queremos probar:

const englishCode = "en-US";
const spanishCode = "es-ES";
function getAboutUsLink(language){
    switch (language.toLowerCase()){
      case englishCode.toLowerCase():
        return '/about-us';
      case spanishCode.toLowerCase():
        return '/acerca-de';
    }
    return '';
}
module.exports = getAboutUsLink;

Puse esto en el archivo index.js . Podemos escribir pruebas en el mismo archivo, pero una buena práctica es separar las pruebas unitarias en archivo dedicado.

Los patrones de nomenclatura  incluyen {filename}.test.js y {filename}.spec.js. Utilice el primero, index.test.js:

const getAboutUsLink = require("./index");
test("Returns about-us for english language", () => {
    expect(getAboutUsLink("en-US")).toBe("/about-us");
});

Primero, necesitamos importar la función que queremos probar. Cada prueba se definía como una invocación de  función test.  El primer parámetro es el nombre de la prueba para su referencia. El otro es una función de flecha donde llamamos a la función que queremos probar y especificamos que resultado esperamos.

En este caso, llamamos a la función getAboutUsLink con en-US como parámetro de lenguaje. Esperamos que el resultado sea /about-us.

Ahora podemos instalar el Jest CLI globalmente y ejecutar la prueba:

npm i jest-cli -g
jest

Si ve el error relacionado con la configuración, asegúrese de tener presente  el archivo package.json.  En ese caso que no lo hagas, genera uno usando npm init.

Debes ver algo como esto:

 PASS  ./index.test.js
  √ Returns about-us for english language (4ms)
  console.log index.js:15
    /about-us
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.389s

¡Buen trabajo! Este fue la primera prueba unitaria simple JavaScript desde el principio al fin. Si tienes instalado la extensión Visual Studio Code, ejecutará la prueba automáticamente una vez salvado el archivo. Vamos a intentarlo extendiendo la prueba con esta línea:

expect(getAboutUsLink("cs-CZ")).toBe("/o-nas");

Una vez salvado el archivo,  Jest te informará que la prueba ha fallado. Esto te ayuda a descubrir potenciales problemas, inclusive antes de cometer un cambio.

Probando funcionalidad avansada y servicios de simulación

En la vida real, los códigos  de idioma para el método getAboutUsLink no serían constantes en el mismo archivo. Su valor se usa típicamente en todo el proyecto, por lo que se definirían en su propio módulo y se importarían a todas las funciones que las usan.

import { englishCode, spanishCode } from './LanguageCodes'

Puedes importar estas constantes dentro de la prueba de la misma manera. Pero la situación se complicaría si trabajas con objetos en lugar de simples constantes. Echa un vistazo a este método:

import { UserStore } from './UserStore'
function getUserDisplayName(){
  const user = UserStore.getUser(userId);
  return `${user.LastName}, ${user.FirstName}`;
}

Este método utiliza  UserStore importado:

class User {
    getUser(userId){
        // logic to get data from a database
    }
    setUser(user){
        // logic to store data in a database
    }
}
let UserStore = new User();
export { UserStore }

En orden, para hacer una prueba unitaria de este método, necesitamos simular UserStore. Una simulación es un substituto del objeto original. Nos permite separar dependencias y datos reales de los métodos probados implementados justo como los dummies ayudaban a las pruebas de choquen de autos en lugar de personas reales.

Si no usamos el simulacro, estaríamos probando tanto esta función como la tienda. Eso sería una prueba de integración y probablemente tendríamos que simular una base de datos utilizados.

Simulación de servicios

Para simular objetos, puede proporcionar una función de simulación o una simulación manual. Me centraré en esto último, ya que tengo un caso de uso simple y llanamente. Pero siéntase libre de echar un vistazo a otras posibilidades de simulación que Jest ofrece.

jest.mock('./UserStore', () => ({
    UserStore: ({
        getUser: jest.fn().mockImplementation(arg => ({
            FirstName: 'Ondrej',
            LastName: 'Polesny'
        })),
        setUser: jest.fn()
    })
}));

Primero, necesitamos especificaciones que hemos simulado- el módulo ./UserStore.  Siguiente, necesitamos regresar la simulación que contiene todos los objetos exportados del módulo.

En esta muestra, es únicamente el objeto User nombrado UserStore  con la función getUser. Pero con las implementaciones reales, la simulación podría ser mucho más larga.  Cualquier función que no  importe  el resultado de la prueba unitaria puede ser fácilmente simulada con jest.fn().

La prueba unitaria para la función getUserDisplayName es simular a la que hemos creado antes:

test("Returns display name", () => {
    expect(getUserDisplayName(1)).toBe("Polesny, Ondrej");
})

Tan pronto como he salvado el archivo, Jest me dice que ha pasado dos pruebas. Si tú estás ejecutando las pruebas manualmente,  hazlo ahora y asegúrate que tienen el mismo resultado.

Informe de cobertura de código

Ahora que sabemos como probar el código JavaScript, es bueno cubrir la mayor cantidad de código como sea posible con pruebas. Y eso es difícil de  hacer. Al final, solo somos personas. Queremos que nuestras tareas y las pruebas unitarias generalmente producen una carga de trabajo no deseada que tendemos a pasar por alto. La cobertura de código es una herramienta que nos ayuda a combatir eso.

La cobertura de código te dirá que tan grande es la proporción de tu código, es cubierta por las pruebas unitarias. Tomemos, por ejemplo, mi primera prueba de unitaria comprobando la función getAboutUsLink:

test("Returns about-us for english language", () => {
   expect(getAboutUsLink("en-US")).toBe("/about-us");
});

Revisa el link en inglés, pero la versión en Español no se ha probado. La cobertura de código es 50%. La otra prueba unitaria es verificar la función getDisplayName a fondo y su cobertura de código es el 100%. En conjunto, la cobertura total de código es del 67%. Tenemos  3 casos de uso para probar, pero nuestras pruebas solo cubren dos de ellas.

Para ver el reporte de cobertura de código, escribe el siguiente comando en la terminal:

jest --coverage

O, si tú estás usando Visual Studio Code con la extensión Jest, tú puedes ejecutar el comando (CTRL+SHIFT+P) Jest: Toggle Coverage Overlay. Le mostrará justo en la  implementación que líneas de código no están cubiertas por la prueba.

code-coverage-inline

Ejecutando la revisión de cobertura, Jest también creará un reporte HTML. Encuéntralo en la carpeta del proyecto en coverage/lcov-report/index.html.

code-coverage

Ahora, no tengo que mencionar que debe esforzarse por obtener una cobertura de código del 100%, ¿verdad? :-)

En resumen

En este artículo, muestra como empezar con las pruebas unitarias en JavaScript. Si bien es bueno que su cobertura de código brille al 100% en el informe, en realidad, no siempre es posible(significativamente) llegar ahí. El objetivo es permitir que las pruebas unitarias te ayuden a mantener tu código y de asegurarte de que siempre funcione como es previsto. Te permiten:

  • claramente, define la implementación de los requerimientos.
  • diseñar mejor su código y separación de preocupaciones
  • descubrir los problemas que puede introducir con tus nuevos commits.
  • y darte la confianza de que tu código trabaja.

El mejor lugar para empezar es la página  Getting started en la documentación Jest así tú puedes probar estas prácticas por ti mismo.

¿Tienes tu propia experiencia con pruebas de código? Me encantaría escucharlo, déjamelo saber en twitter o únete a alguno de mis streams Twitch.