Pruebas unitarias en Frontend

Pruebas unitarias en Frontend

Muy bien, ahora eres Desarrollador(a) Frontend. Has realizado algunos proyectos aquí y allá e implementado algunas aplicaciones que te ayudarán a comprender los conceptos esenciales del desarrollo, pero hay algo que no pusiste en práctica aún, quizás porque lo evitabas hasta ahora o simplemente porque no entiendes y no conoces los beneficios que puede traer. Hablo de las temidas pruebas, específicamente de las Pruebas Unitarias de Frontend, una parte muy importante del ecosistema de pruebas.

Las pruebas en Frontend se pueden dividir en 3 categorías:

Existe este concepto llamado Pirámide de Prueba, donde la idea es que comiences en la parte inferior de la pirámide y luego escales tus pruebas. ****El número de pruebas disminuye a medida que se asciende en la pirámide, ya que aumenta el esfuerzo y el tiempo de ejecución necesarios para cada tipo de prueba.

También es interesante notar que vemos más pruebas unitarias en la base, ya que son más rápidas de desarrollar y ejecutar. Y es sobre ellas de las que hablaremos con más detalle hoy.

Pero antes, entendamos qué es cada tipo de prueba:

  • Pruebas E2E o pruebas de extremo a extremo: Es la práctica de probar si una aplicación se ejecuta según lo previsto de principio a fin. Se prueba toda la aplicación, como la comunicación entre componentes, base de datos, red, APIs, etc., y además ejecuta tu código en varios tipos de navegadores, ya que puede ocurrir que existan algunas diferencias de renderizado entre uno y otro.
  • Integration Testing o Pruebas de Integración: Ocurren cuando los componentes se prueban como un grupo para garantizar que no haya ruptura entre lo que se hizo de manera unitaria y lo que se está integrando en conjunto. Idealmente, las pruebas unitarias deben realizarse primero y luego las pruebas de integración para verificar que los componentes funcionarán juntos.
  • Unit Testing o Pruebas Unitarias: Las pruebas unitarias son códigos que prueban pequeñas partes de tu código (al que podemos llamar unidad). Dado un comportamiento o entrada de esa unidad, pruebas la regla comercial o la salida de esa unidad. Estas unidades a menudo toman la forma de métodos, propiedades, acciones de elementos de la interfaz de usuario, etc.
⚠️
La estructura frontal de una aplicación web podría compararse con un rompecabezas. Si una de las piezas se rompe de tal forma que ya no se puede acoplar a la otra pieza, la imagen se verá comprometida y será necesario reparar la pieza rota.

Entendiendo las Pruebas Unitarias

Las pruebas unitarias son las pruebas más básicas en la cadena de prueba en el desarrollo Frontend. Al contrario de lo que nos aportan otros tipos de pruebas, las pruebas unitarias aseguran que un pequeño fragmento de código, también llamado unidad, está funcionando aunque esté desconectado de toda la aplicación.

Podemos considerar una unidad como la parte más pequeña de un código. Podría ser una función o un botón, por ejemplo.

Cuando desarrollamos aplicaciones Frontend, a menudo trabajamos con componentes. Las pruebas unitarias luego aseguran que funcionen como se espera de forma aislada, pero no olvides que también necesitas saber si estos componentes funcionan cuando se usan juntos.

El principal objetivo de las pruebas es ayudarnos a garantizar una entrega de calidad y, aunque dedicaremos un tiempo en la ejecución de las pruebas, al final del día ahorraremos tiempo a todo el equipo.

Piensa conmigo, subimos una aplicación a producción, una página de inicio de sesión por ejemplo y, debido a algún cambio que se hizo en el componente de entrada, este inicio de sesión deja de funcionar. Con eso, el usuario ya abrió un ticket de soporte, el equipo de soporte dedicó aproximadamente media hora a comprender cuál era el problema, luego el dev responsable dedicó otra hora a resolver el problema y pasar a producción, para luego la persona responsable del servicio dar una retroalimentación al usuario.

¿Te das cuenta de que detuvimos a 3 empleados de sus funciones para resolver un problema que podría haberse detectado si se hubieran realizado algunas pruebas?
Esta es la mentalidad que hay que implantar en los equipos. Piensa en los beneficios en lugar del tiempo "perdido" al realizar la prueba.

Escribe código fácil de probar

Partimos del famoso concepto Keep it simple, stupid y lo hacemos teniendo en cuenta algunos principios a la hora de desarrollar, como por ejemplo, mantener una única responsabilidad para cada pieza de código (un botón debe ser sólo un botón, no un enlace), procurar tener menos dependencias entre las diferentes partes del código y, aún más importante para el Frontend, mantener la lógica comercial separada de la interfaz de la aplicación.

Cuando escribimos código que es fácil de probar, en consecuencia tenemos uno más legible, más simple de mantener y de agregarle nuevas características.

Otra forma de tener un código claro y fácil de leer es tener tu desarrollo guiado por pruebas, es decir, escribir las pruebas incluso antes de implementar el código. Esto es lo que conocemos como TDD (Test Driven Development). De esta manera, primero pensamos en el comportamiento que esperamos para cierta funcionalidad de la unidad y luego escribimos una prueba basada en eso.

Este flujo también se puede utilizar para hacer refactorizaciones, ya que garantiza que ese fragmento de código solo haga lo que se espera de él y que funcione correctamente.

Otro consejo es pensar siempre que todo en las pruebas es una simulación. Sería como una réplica de las funcionalidades, excepto que todos los datos y acciones son "simulados" o simplemente falsos.

Excelente, ¿por dónde comenzamos?

Desarrollar una aplicación que ya tiene una base de pruebas bien pensada es maravilloso en teoría, pero lamentablemente, sabemos que en la práctica es mucho más complicado. ¡A menudo, la mayor parte de la aplicación ya se ha escrito y prácticamente no se ha probado! Entonces, ¿cómo saber por dónde empezar a probar lo que ya existe?

En ese caso, tendría más sentido empezar por el final. ¿Pero cómo? Usando pruebas E2E (end-to-end) para asegurar que la aplicación funcione como espera el usuario, probando los flujos principales de la aplicación y asegurando que seguirán funcionando cuando hagamos cambios o agreguemos alguna característica nueva.
Una vez hecho esto, podemos agregar los otros tipos de pruebas durante el desarrollo y equilibrarlos gradualmente.

Una excelente manera de saber qué probar es comenzar desde cero observando los componentes o flujos de una aplicación. "¿Qué toma mi método como entrada y cuál es su salida?", "¿mi método afecta el estado de mi componente?", "¿cuáles son los casos de uso?". Estas son buenas preguntas para encontrar un punto de partida.

Y recuerda, no importa cuál estrategia elija tu empresa, lo importante es iniciar una conversación sobre el tema y fomentar una cultura de pruebas, pero siempre teniendo en cuenta que todo el equipo es responsable de la calidad de la aplicación.

La estructura de una prueba unitaria

Antes de construir nuestra primera prueba unitaria, comprendamos cuál es la estructura básica de una prueba. Si quieres seguirla y probarla en tu máquina, te recomiendo la web de TDDBin donde puedes simular pruebas gratis y sin necesidad de instalar nada.

Entendamos qué se escribió:

1) El método que queremos probar. Como señalamos anteriormente, las pruebas unitarias a menudo se aplican a métodos, componentes o interacciones de elementos de la interfaz de usuario.

2) Un conjunto de pruebas, que agrupan las pruebas unitarias relacionadas. Por ejemplo, un conjunto de pruebas puede incluir todas las pruebas que pertenecen a un método específico. Puedes declarar tantos conjuntos de prueba como desees, tu objetivo es hacer que tus registros de prueba sean más legibles.

3) Una prueba unitaria acompañada de una descripción. La declaración (4) dentro de la llamada de retorno es la prueba en sí.

4) Una afirmación de la prueba. Las pruebas tienen que ver con afirmaciones, es decir, comparamos un cierto valor con un valor esperado. Aquí damos el valor de retorno de nuestro método add con 1 y 1 como parámetros y esperamos que el resultado sea 2.

Sencillo, ¿verdad? Esta es la estructura básica que usaremos a continuación para construir nuestras pruebas.

Requisitos

Para este tutorial, necesitas Node.js (v6 y superior) y npm instalados en tu equipo, además de conocimientos básicos de React.js.

Ejemplo real de una prueba unitaria

El ejemplo mostrado anteriormente se hizo solo para conocer la estructura básica de una prueba. Ahora apliquemos las pruebas en un componente real de principio a fin.

Podemos encontrar varias herramientas de prueba en el mercado. Hoy vamos a utilizar las más comunes: Jest y Enzyme.

Conociendo las herramientas

Jest es una herramienta de prueba de Facebook que facilita la realización de pruebas unitarias de JavaScript. Enzyme es una herramienta específica para React que proporciona varios métodos útiles que mejoran la forma como probamos los componentes de React.

Veamos cómo se pueden usar Jest y Enzyme para crear aplicaciones React más sólidas.

Para crear la plantilla de nuestra aplicación donde construiremos las pruebas, usaremos create-react-app.

Escribe en tu terminal:

npx create-react-app unit-tests

Luego abre la aplicación en tu navegador preferido.

Normalmente, necesitaríamos instalar y configurar Jest antes de escribir cualquier prueba, pero el uso de create-react-app Jest ya está instalado, por lo que no necesitamos hacer nada más. Entonces podemos pasar directamente a escribir nuestra primera prueba.

Observando las carpetas de la aplicación encontrarás el archivo App.test.js, que ya viene con una prueba creada:

En el terminal del editor de código, ejecuta npm run test.

Deberías ver este resultado:

¡Felicidades! Ejecutaste tu primera prueba unitaria.

Crea tu primera prueba unitaria

Construyamos un contador básico que permita al usuario hacer clic en un botón y aumentar el valor que aparece en la pantalla.

En el archivo App.js cambiaremos el código existente por lo siguiente:

Escribe en la terminal:

npm start

Tu aplicación se ejecutará en localhost:3000 y el resultado será:

Antes de comenzar a escribir nuestras propias pruebas, debemos instalar Enzyme, la otra herramienta de la que hablamos anteriormente que nos ayudará a construir las pruebas:

npm install enzyme enzyme-adapter-react-16 --dev

Después de la instalación, debemos crear el archivo setupTests.js dentro de la carpeta src.

Este archivo le dice a Jest y Enzyme cuáles adaptadores vamos a usar. create-react-app se ha configurado para ejecutar este archivo automáticamente antes de cualquiera de nuestras pruebas para que Enzyme esté configurado correctamente.

El archivo debe contener el siguiente código:

¡Vamos a empezar!

Pregúntate cuáles aspectos del componente que construimos podemos probar. En este caso serían:

- Lo que se muestra inicialmente en la pantalla.
- El valor debe aumentar al hacer clic en el botón.

Ahora, vayamos al archivo src/App.test.js y cambiemos su contenido a:

Aquí estamos usando la renderización superficial (superficial) de Enzyme para probar el estado inicial de la aplicación. Esencialmente, solo representamos el código definido dentro de ese componente; todo lo que se importe desde otro lugar no se incluirá.

En el código anterior, la representación shallow de nuestro componente de aplicación se almacena en la variable wrapper creada al comienzo de la prueba. Luego obtenemos el texto dentro de la etiqueta h1 del componente y verificamos si es el mismo texto pasado dentro de la función toContain.

Hagamos esta prueba para ver el resultado:

Vamos a detallar más la prueba para una mejor comprensión:

1) Como mencionamos antes, Jest nos permite crear un conjunto de pruebas.

2) A veces, es posible que desees configurar algo antes de realizar la prueba. Es por eso que usamos beforeEach, por lo que siempre antes de ejecutar las pruebas renderizaremos el contenido que está dentro de la función. En este caso, comprobamos si la renderización de nuestro componente está ocurriendo.

3) Nuestra primera prueba es simple. Verificamos si dentro de nuestro componente que está envuelto en la variable wrapper, encontraremos (find) la etiqueta h1 y el texto que se muestra inicialmente en la aplicación es igual a 0. Simple, ¿verdad?

Probando la interacción del usuario

Ahora escribamos una nueva prueba que simule al usuario haciendo clic en el botón y luego nuestra salida se incrementará en 1.

Debajo de la primera prueba agrego el siguiente código:

En esta prueba estamos usando la función simulate() para simular un click de botón. También ingresamos lo que esperamos que suceda después del clic, que nuestro texto en la etiqueta h1 que anteriormente mostraba 0 ahora es igual a 1.

⚠️
Consejo: la mayoría de los tipos de eventos se pueden simular mediante el método simulate(), incluidas las entradas, los clics, el enfoque, los desenfoques, los desplazamientos, etc.

Si revisas tu terminal, verás que las pruebas funcionaron como se esperaba:

¡Ahora, hagamos algo un poco diferente! Agregaremos una prueba de funcionalidad a la aplicación que aún no existe y luego escribiremos el código para que pase la prueba. Aquí aplicaremos y probaremos la metodología de TDD - Test Driven Development.

Crea otra prueba dentro de la función describe() con el siguiente código:

En este momento en la terminal ya debería haber aparecido el error con la siguiente frase: Method "``simulate``" is only meant to be run on a single node. 0 found instead.

¡No tengas miedo! Esto solo indica que se llamó al método de simulate() dentro de un elemento que aún no existe.

Ahora vayamos a nuestro componente App.js y escribamos el código que hará que nuestra prueba se desarrolle:

Tenga en cuenta que cambiamos la clase de botones, creamos una función decrement() y agregamos un nuevo botón que disminuirá nuestro contador en 1.

En este punto, todas nuestras pruebas deberían realizarse con éxito:

Estas fueron algunas pruebas simples en un componente, pero nos brindan una base genial para continuar estudiando y aprender más funciones, más trucos sobre herramientas de prueba y varios otros tipos de pruebas que podemos crear. Recordando que las pruebas de este tipo se pueden aplicar en varias unidades de la aplicación.

Bonus track: probando un componente React con snapshot

Las pruebas de instantáneas ayudan a verificar que la salida renderizada de un componente sea correcta en todo momento. Cuando ejecutamos una prueba de snapshot, Jest hace que se pruebe el componente React y almacena su salida en un archivo JSON.

En las próximas pruebas, Jest verificará que la salida del componente no se desvíe de lo que se guardó previamente. Si cambia la salida del componente, Jest lo notifica y puede actualizar la prueba a la última versión o corregir el componente para que coincida con la instantánea nuevamente.

Esto nos ayudará a evitar cambios accidentales en los componentes, muy importante especialmente cuando estamos hablando de desarrollar grandes aplicaciones Frontend.

Para usar la función de snapshot de Jest, necesitamos un paquete adicional, react-test-renderer, que podemos instalar de la siguiente manera:

npm install react-test-renderer --dev

Después de la instalación, debemos importar el renderizador al comienzo de nuestro archivo de prueba App.test.js:

import renderer from 'react-test-renderer';

Ahora crea la prueba a continuación antes de todas las creadas anteriormente:

La primera vez que se ejecuta esta prueba, todavía no hay una instantánea para este componente, por lo que Jest la creará. Puede verificar el contenido de la instantánea dentro de la carpeta src/__snapshots__.

Abre el archivo App.test.js.snap.

Puede ver que el contenido renderizado es el output del componente de la aplicación y se guardará en este archivo. La próxima vez que se ejecute la prueba, Jest confirmará que los outputs son los mismos.

Conclusión

Observamos cómo Jest hace que las pruebas de los componentes de React sean mucho más fáciles y optimizadas, y cómo usarlo junto con Enzyme para pruebas unitarias y pruebas de snapshot.

Creo que esta ha sido una buena introducción a las pruebas unitarias Frontend, y espero que te haga querer aprender más sobre el tema.

La cultura de pruebas puede parecer una práctica que requiere mucho tiempo y mucha responsabilidad y, al principio, esto es cierto. Pero créeme, eventualmente te darás cuenta de cuán relevante es probar una aplicación y cómo te ayudará a depurar y estructurar tu código, así como ahorrar tiempo, reducir la deuda técnica, mejorar su flujo de trabajo y aumentar su productividad a largo plazo.

Saber que tenemos un recurso que agrega confiabilidad a nuestro código y que aporta más calidad a la aplicación es muy satisfactorio. ¡Pruébalo!

Gracias por leerme. Espero que este artículo te haya ayudado 😊

⚠️
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Revelo.

Revelo Content Network da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.