Actualmente, en el mundo java, solemos tener en nuestra «caja de herramientas» de tests ciertas librerías que nos pueden ayudar a limpiar y generar menos código en los tests. Para realizar tests unitarios la gran mayoría utilizamos Junit5 con el apoyo de Mockito para mocks, quizás también el patrón Mother para organizar la creación de objetos, etc…
En este tutorial vamos a ver algunas librerías de testing interesantes que pueden ser parte importante de vuestra caja de herramientas a partir de ahora
Presentación de los actores de este tutorial
Mockito
Mockito es «de facto» la librería que utilizamos para crear mocks dentro de java. Los mocks los utilizamos para simular comportamientos de clases y así poder centrarnos en probar unitariamente una única clase. Es especialmente útil cuando las clases realizan llamadas fuera del código, como llamadas a otros servicios o bases de datos.
Podam
Podam es una librería que personalmente me gusta mucho, así que es posible que no sea del todo objetivo. Es un «rellena objetos», en su uso más simplificado le das una clase de datos con cualquier tipo de estructura, objetos anidados, listas, mapas y te devuelve una instancia con todos los datos rellenos. Por defecto lo rellena con datos aleatorios en base a su tipo. Esto impacta en la cantidad de lineas en la mayoría de los tests unitarios ya que pasamos de tener 50 lineas de seteo de valores a una única linea en muchos casos. Podam también permite customización por si necesitáramos algo muy específico.
En base a mi experiencia hay muchos tests unitarios (no todos) en los que lo único que necesitamos es tener objetos rellenos y realmente no importa mucho si los datos que contienen son reales o son «test name» por lo que en base a tener código más limpio a nivel de tests esta librería me parece muy interesante.
https://mtedone.github.io/podam/
Datafaker
Anteriormente se llamaba Javafaker, por lo que si buscáis más información al respecto es posible que aparezcan más cosas con el nombre antiguo. Esta librería es una colección grande de datos. Inicialmente nos evita tener que inventarnos datos para nuestros tests, me refiero a los datos que acaban siendo «testname, testsurname, addresstest». Tiene cientos de colecciones a las que se accede mediante métodos que nos dan desde un nombre completo de una persona, a una dirección, teléfonos, colores, etc… Con esto evitamos tener los típicos «test name» y pasamos a usar algo así faker.name().fullName();. Este uso simple puede llegar a dejar muy limpios y simplificados los datos de nuestros tests, además en caso de errores queda más bonito que en el campo «address» aparezca una dirección correcta que el texto «test123»
https://www.datafaker.net/documentation/getting-started/
Junit5
Para este tutorial utilizaremos como base de los test Junit5 que es lo más utilizado a nivel general, habría sido posible utilizar una alternativa como Testng, os dejo comparativa por aquí https://www.lambdatest.com/blog/junit-5-vs-testng/. Actualmente no veo diferencias notables entre ambos, es cierto que Testng tiene más funcionalidades que Junit4, pero con Junit5 se han equiparado, quitando algún cambio en nomenclatura no creo que una librería aporte más que otra de forma significativa.
Qué vamos a hacer
Entonces, como resumen, utilizaremos Junit5 como base de tests unitarios, utilizaremos sus anotaciones y sus asserts, mockearemos métodos con Mockito. Los objetos que devolvamos en los mocks, los generaremos con Podam con datos aleatorios y en algún caso customizaremos estos datos para que por ejemplo un campo «Address» tenga una dirección real.
Proyecto base
Utilizaremos un proyecto base realizado con Springboot que tendrá un controlador y un servicio, algo muy simple ya que no nos interesa el proyecto en sí, si no más bien ver el uso de nuestras librerías de tests.
Podéis acceder a él desde este link https://github.com/raulvillalbamedina/mockito-and-more-sample
Tests a realizar
Vamos a realizar tests unitarios, específicamente de caja negra. Esto implica que son tests en los que vamos a acceder solamente a métodos públicos de nuestras clases y vamos a realizar validaciones únicamente sobre los objetos que devuelvan estos métodos.
Vamos a tener una clase de test por cada clase a probar, en nuestro caso serán 2 clases de tests, una para el controlador y otra para el servicio. En estas clases tendremos varios tests y tendremos siempre en mente los siguientes objetivos:
- Máxima cobertura posible, si es 100% mejor. La idea de cubrir con tests unitarios la mayor cantidad de código posible viene de verificar que no hay errores graves en nuestro código, si los tests pasan por todas las lineas verificamos esta cuestión.
- Validar el comportamiento de cada método por separado. Solo con la cobertura no es suficiente para validar que un método funciona correctamente, normalmente hay que validar que el resultado que devuelve es el esperado. En los casos en los que los métodos realizan operaciones internas que no impactan en el resultado o directamente son métodos que no devuelven nada, debemos realizar verificaciones que confirmen que se han realizado las acciones que esperábamos.
Configuración de las librerías
Vamos a explicar inicialmente cómo añadir estas librerías a nuestro proyecto.
Mockito
Se añaden la librería de test de spring-boot al pom de la siguiente manera:
Y ya podemos probar que tenemos acceso a las anotaciones de Mockito en nuestros tests.
Datafaker
Se añade la librería al pom:
En principio no necesitamos nada más para poder utilizar la librería
Podam
Se añade la librería al pom:
Podam puede tener configuración general, crearemos una clase de «utilidad para los tests» que contendrá esta configuración. De este manera crearemos la factoria de podam una única vez para todos los tests. Como configuración especial indicaremos que las listas deberán tener siempre 3 elementos y que cada vez que se necesite una instancia del mismo objeto se cree una nueva.
El test del controlador
Nuestro controlador tiene como dependencia el servicio, así que necesitaremos mockear la respuesta del servicio de la siguiente manera:
Además solo tenemos un método público que no contiene caminos lógicos por lo que únicamente necesitamos realizar un test que verifique que la respuesta del servicio se devuelve correctamente por el controlador.
El test del servicio
Nuestro servicio contiene una dependencia, utilizaremos Mockito para independizarnos de ella. Con Podam y Datafaker vamos a generar los datos de los parámetros y la respuesta, así validaremos que devuelve un resultado correcto.
Ejemplo con Podam:
Ejemplo con DataFaker:
Debido a que existen varios caminos lógicos que modifican el resultado realizaremos tantos tests como sean necesarios para pasar por todas las casuísticas.
En ambas clases de tests hemos añadido los mismos tests utilizando Podam y Datafaker.
Si ejecutamos los tests en debug, podremos ver que cada herramienta funciona de una manera distinta.
Datos de DataFaker:
Datos de Podam:
Siendo Podam una utilidad que genera datos totalmente aleatorios que podríamos llamarlos «basura» que solo nos sirven para rellenar de forma sencilla objetos que no nos importan demasiado. Por el contrario DataFaker nos brinda datos «más realistas» que nos permiten realizar juegos de datos amigables, quizás para aplicar el patrón Mother, aunque eso sí conllevan más trabajo que si los realizáramos con Podam.
Conclusión
Aquí hemos mostrado unas pinceladas de librerías de utilidad que nos pueden llevar a tener mucho menos código en nuestros tests y mucho más amigable. Como todo gran poder, conlleva una responsabilidad y sobre todo realizar un estudio de en qué lugares de nuestro código es bueno, correcto e interesante aplicar cada cosa. Para mí una señal para empezar a mirar de añadir alguna de estas librerías es cuando empiezo a ver tests que contienen mucho más código de «preparación» de clases de datos que código de validaciones en sí.
Por último comentar que el uso general de Podam y Datafaker es para el ámbito de tests, pero nada nos impide utilizarlo para la creación de «servicios dummy» o pruebas de concepto para validar y probar arquitecturas de manera temprana.