Capitulo 4 - Testing
Capitulo 4 - Testing
Capitulo 4 - Testing
SOFTWARE TESTING
Cuando pensamos en testing en software, pensamos en algo que nos de garantías de que la aplicación que estamos
construyendo funcione bien. Además de que funcionen bien en nuestra computadora, nos vamos a asegurar que
cuando lo deployemos, también funcionen en el destino o que si fallan algunos tests directamente no lleguen las nuevas
actualizaciones al servidor de producción, por ejemplo. También podemos hacer del testing una filosofía de vida cómo
veremos más abajo, pero en general testear bien nuestra aplicación nos da más que sólo garantías, nos proveen una
documentación viva de nuestro código.
¿Qué cosas testear? ¿Cuántos tests hacer?: Las respuestas a estas preguntas van a variar según el proyecto que estén
haciendo y sus necesidades, pero en general pueden seguir las guidelines seteadas en la siguiente pirámide:
En la pirámide vemos los tipos de pruebas y la cantidad de ellas que debemos hacer en nuestra aplicación. En general la
mayoría de los tests que hagamos va a ser de tipo unit tests, en segundo lugar, Integration Tests y por último End-to-
End.
Unit Testing: Un test unitario o unit test es un pedazo de código automático que invoca a una unidad de trabajo del
sistema y chequea que el resultado de esa unidad sea el esperado. Una unidad de trabajo es cualquier función lógica del
sistema que no pueda ser separada en piezas más pequeñas y que puede ser invocada por alguna otra interfaz. Esta
unidad puede ser sólo un método, o el comportamiento de una clase entera, o el comportamiento de varias clases
interactuando entre ellas para lograr un propósito, que puede ser verificado. La presencia de tests unitarios habla de
que el software fue construido de forma modular. Cuando escuches que alguien dice que el software debería ser más
testeable se refiere a esto. Un buen test unitario debería ser:
• Completamente automatizable.
• Poder ejecutarse en cualquier orden en conjunto con otroso tests.
• Siempre retorna el mismo resultado, no importa cuantas veces lo corra.
• Es rápido.
• Testea un solo concepto lógico del sistema.
• Es fácil de entender al leerlo.
• Es fácil de mantener.
Si cuando estamos escribiendo los tests nos damos cuenta que tenemos que crear muchos mockups, es una señal que
nuestro diseño no es muy modular, y que tal vez se puede mejorar.
• Ventajas: Obviamente, armar todos los tests y correrlos nos puede poner un overhead en nuestro proyecto. Si
el proyecto es chico, entonces podemos probar todo a mano y darnos cuenta rápido si un cambio afecta o no a
las demás partes de la aplicación, pero a medida que el proyecto va a creciendo, se hace más complicado probar
todo manualmente y más time-consuming. En esos casos hacer Units Tests automáticos nos va a brindar los
siguientes beneficios:
➢ Nos va a permitir hacer cambios grandes en el código rápidamente. Cuando los hacemos, corremos los
tests de nuevo y vemos rápidamente si todo funciona bien o no. Nos puede salvar varias horas de
debugeo.
➢ Nos da un límite para decidir cuando una funcionalidad está terminada o no. Si pasa el test, entonces
pasamos a la siguiente feature.
➢ Tenés feedback inmediato de tu código, podes ver exactamente dónde está fallando y hasta usar eso
para continuar trabajando donde dejaste la última vez.
➢ Si ya tienes experiencia armando tests, vas a poder codear más rápido, ya que tienes en la cabeza
exactamente qué quieres lograr antes de empezar a codear.
➢ Si tienes buenos tests definidos, estás muy cerca de tener una buena documentación, ya que en los
tests implícitamente estás diciendo qué debería hacer cada cosa.
➢ Te va a ayudar a reutilizar tu código en otro proyecto. Cuando empiezas un proyecto nuevo, lleva tu
código y tus tests, refactoriza el código en el nuevo proyecto hasta que los tests pasen de nuevo.
Integration Test: Estos tests prueban el funcionamiento de distintas unidades combinadas, por ejemplo, la interacción
entre un módulo y sus dependencias.
End to End Tests: Estos tests sirven para probar la aplicación en su totalidad, es decir, usarla como si fueras el usuario
final y probar si todo está funcionando bien. Por ejemplo, si estuviéramos trabajando en un eCommerce, probaríamos si
podemos loguearnos, buscar un producto, agregarlo al carrito, y comprarlo.
Test Driven Development (TDD): Es una técnica para construir software que se basa en escribir tests. Básicamente
consiste en el siguiente ciclo:
• Agregar un test nuevo: En TDD, para cada nueva feature vas a escribir un nuevo test, para hacerlo el
desarrollador está obligado a conocer en detalle las especificaciones y requerimientos de esa feature. Esto es
clave y es el gran diferenciador entre escribir el test después que hayas escrito el código, de esta forma tienes
todo definido antes de empezar por la primera línea de código.
• Correr todos los test y fijarse si el nuevo falla: Si ya teníamos otros tests, antes de empezar a codear nos
fijamos si realmente el nuevo test NO pasa. En caso contrario estaríamos implementando funcionalidad que ya
está definida o funcionalidad que no sirve, o tal vez el test esté mal escrito.
• Escribir el código: Ahora sí, empezamos a escribir código de tal manera que logremos que el test pase. En esta
etapa no importa la elegancia del código, sólo queremos pasar el test.
• Correr los tests: Si todos los test corren, incluido el nuevo, el desarrollador está seguro que el nuevo código
cumple con los requerimientos de la funcionalidad y además que los nuevos cambios no rompen ninguna otra
feature ya existente.
• Refactorizar el código: Una vez que hayamos pasado el test, vamos a refactorizar el código para que sea lo más
legible y performante posibles, y que mantengan la convención de nombres y patrones de todo el proyecto.
• Repetir: Ahora pasamos a un nuevo test, es decir vamos a agregar un nuevo feature o funcionalidad y vamos a
empezar desde el primer paso.
Como siempre, existen variaciones sobre este standart, de hecho, hay dos grandes concepciones, la de codear primero y
escribir los test despues, o al reves. Si escribimos los units tests primero y una vez que terminamos empezamos a codear
el nuevo feature nos aseguramos que entendemos bien el problema antes de empezar a codear la solución.
Testing Frameworks: Existen muchas herramientas que nos van a ayudar a automatizar la creación, ejecución y control
de los tests unitarios. De hecho, existen herramientas que sirven como 'ambientes' de prueba, y con ellas vamos a poder
armar el workflow de tests (agrupar tests, ejecutar los tests automáticamente antes de hacer un deployment, etc..) y
también existen librerías de Aserción (assertion), que nos van a servir para comprobar si el output esperado de una
función condice con el output real. En programación una aserción o assertion es un predicado (expresión que devuelve
verdadero o falso), incluido en un programa y que generalmente compara el resultado o el estado esperado de algo
contra el real en el momento de ejecución.
JEST
JEST es un framework de testing de fácil uso, pero a la vez con muchísimas posibilidades. Viene con una librería de
assertion integrada por lo que no tendremos que configurar ninguna.
Luego ejecutamos npm test y ya estaríamos corriendo los tests, como inicialmente no vamos a tener ninguno por
consola nos dirá que no ha encontrado tests. En breve explicaremos como armar nuestro primer archivo de test, pero
antes, el comando jest admite muchas opciones o flags entre los cuales vamos a mencionar los siguientes:
• Correr solo los archivos de tests que matcheen con determinado patron dentro de su nombre: jest test-
pattern
• Correr un determinado archivo de test mediante su path: jest path/to/test.js.
• Correr solo un test mediante su nombre: jest -t name-spec.
• Correr en modo 'watcher': jest --watch o jest --watchAll (El primero solo correra los tests que fueron afectados
por alguna modificación desde la última vez que hicimos cambio en el código).
• Agregar un resumen de cada archivo de test: jest --verbose (En el caso de ser un único archivo
automáticamente lo hace sin necesidad del flag).
Ejemplo: Crearemos un archivo sum.js y dentro de él una función sum que la exportaremos para poder utilizarla luego
en el archivo de tests:
Luego crearemos otro archivo, sum.test.js donde definiremos los tests que luego ejecutará JEST:
Si ahora ejecutamos npm test (configurar previamente el package.json como mostramos antes) deberían ejecutarse los
tests.
Si analizamos la estructura del ejemplo anterior usamos algunas palabras que hasta hoy no conocíamos, como por
ejemplo it, expect y toBe. Entendamos para que sirve cada uno de ellas:
MATCHERS
JEST tiene distintos matchers para realizar distintas validaciones sobre las funcionalidades que queremos probar:
No son los únicos, existen más que pueden consultar en la documentación oficial de JEST. https://jestjs.io/docs/expect
Running Options: Podemos también agrupar tests en "categorías" utilizando la palabra describe, por ejemplo, siguiendo
el ejemplo anterior de la suma podríamos tener casos con números enteros, otros con números decimales y otro con
inputs inválidos. Es posible armar también subcategorías poniendo describes dentro de otros describes.
Skip Tests: En el caso de que algunos tests no queramos que se ejecuten podemos saltearlos de forma individual
colocando xit en vez de it cuando son definidos en el archivo de tests o si no podemos incluso saltear todo un describe
completo colocando xdescribe.
Si observamos ahora la ejecución del comando npm test sum-describe veremos que el segundo test del describe de
'Decimal numbers' y todo el describe de 'Invalid inputs' no se van a ejecutar:
Only: Hay casos en los que solamente queremos probar un tests para evitar que la consola se nos llene de código que
no nos estaría interesando en ese momento, para esto JEST tiene también una opción only para ejecutar únicamente un
test de toda la suite de tests. Volviendo al ejemplo anterior, si solo quisiéramos ejecutar el test should throw an
TypeError if first parameter is not a number podríamos colocarle it.only.
Ahora al ejecutar npm test sum-describe veremos que todo el resto de los tests fueron salteados:
Lo mismo se puede aplicar sobre los describe para ejecutar únicamente un grupo de tests.