Pruebas de unidad con Mockito(Aportación) - [SGCE2013]
En esta ocasión, con la finalidad de aportar algo a la comunidad(y ver si de paso me gano un pase) les quiero poner un tema que les de a conocer una herramienta que puedan usar los que están haciendo desarrollo en Java.
Les platico brevemente el escenario que me ha puesto a escribir de este tema: hace un par de meses me asignaron a la tarea de hacer el mentoring de un grupo de desarrolladores, el objetivo era "aumentar la calidad del software que se desarrollaba", y esto lo fui elaborando a través de varias técnicas: programación en pares, dojos, control de versiones, mediciones de tiempos, pomodoros, etc., y la guía técnica para ir creciendo un proyecto con Java; yo vengo desarrollado Groovy desde hace mucho tiempo y esto se me hizo interesante para medir nuevamente el tiempo de desarrollo de un proyecto con Java. El factor común de todos era Maven y por supuesto Java(en varios niveles de experiencia), lo cual lo hacía más interesante.
En mi humilde opinión, uno de los tantos factores para mejorar la calidad del software que se escribe son las pruebas, y para este equipo eran un mundo desconocido(a excepción de uno de ellos), lo cual fue bueno por que podrían aprenderlas sin vicios y diferenciarlas desde un principio, y malo por que no tenían pruebas de ningún software. Su proceso de desarrollo para determinar si un fragmento de código funcionaba era tardado, y gracias a esto, también fuimos tomando también un mejor ritmo.
Siempre se confunden las pruebas de integración con las pruebas de unidad, yo lo hice, y supongo que fue por el nombre del framework: JUnit. Creo que la parte de Unit es la que nos indica que el framework hace pruebas de "unidad", y las hace realmente, solamente que nuestra concepción debe ampliarse más para entender los preceptos de la terminología. Para ayudarme a explicar esto voy a escribir mi concepción(muy simple) de tres términos:
- Pruebas de unidad: Son del tipo que determinan si un componente funciona de cierta forma esperada, bajos ciertos escenarios controlados y bien definidos, y excluyen el buen o mal funcionamiento de los elementos que colaboran con el mismo, pues no es de la incumbencia del elemento bajo ejecución, pero deben indicarnos que los mensajes a dichos elementos ajenos y externos deben ser enviados.(Aquí recomiendo una referencia al libro de Pragmatic Programmer capítulos 21, 23 y 34).
- Pruebas de integración: Son aquellas que determinan que un componente se ejecuta correctamente incluyendo las interacciones con sus componentes asociados, los cuales también deben estar listos para ser invocados.
- Pruebas de sistema: Son aquellas que ayudan a determinar si un flujo de negocio se ejecuto de manera correcta dadas ciertas condiciones y ambientes, y por lo general incluyen a varios componentes en su estructura.
Siendo esto muy breve, quiero enfocarme en las primeras, las pruebas de unidad, y me voy a basar en un ejemplo, dado que tengo la siguiente prueba:
El código que lo resuelve es muy sencillo:
Como ven, no hay necesidad de llamar a nada más que un método sencillo de un objeto. Hasta aquí nada complicado, pero, ¿que pasa si deseamos hacer uso de un caché?, y esto va con el afán de que ustedes puedan extrapolar este conocimiento a sus componentes de aplicaciones que estén desarrollando y probando, especialmente servicios de negocio y controladores de frameworks MVC.
Supongase que ahora tenemos la siguiente prueba:
Siguiendo la inercia del desarrollo podremos hacer algo como lo siguiente:
Y hacer mediciones de tiempo para determinar que el caché es más rapido que la operación per se, etc.; sin embargo, seguiremos sin conocer si estamos llamando a algún componente que me haga el caché.
NOTA: Todos los elementos o componentes que están involucrados en el código que se esta probando de forma unitaria son conocidos como colaboradores.
Por lo tanto siguiendo un poco de diseño de software simple(Single Responsability), determinemos que necesitamos un colaborador que me permita guardar en caché la operación en cuestión. Y tenemos más de una prueba ahora, por lo tanto podemos reestructurar nuestra prueba de la siguiente manera:
Introduzcamos a Mockito como nuestro bartender en esta prueba, e identifiquemos que la clase que esta en la lupa es Calculadora y que necesita un elemento colaborador de caché, el cual llamaremos CacheDeCalculadora, adicionalmente, necesitaremos usar algún método(que todavía no existe) de dicho colaborador que "ejecute" la persistencia en el caché.
Hagamos uso de Mockito en nuestra prueba:
Si ejecutamos esta prueba, con dichos cambios, saltarán errores de compilación obvios, como el de no se encuentra dicha clase CacheDeCalculadora, y claro, por que no se ha creado. Simplemente agregando una interfaz y el método con dicha firma ejecutaré la prueba para dar pie a las bondades que me ofrece mockito.
Ahora bien, el error que tengo es el siguiente:
Wanted but not invoked:
cacheDeCalculadora.persistirSuma(6, 2, 8);
-> at com.makingdevs.CalculadoraTests.pruebaSumaDeDosNumerosGuardandoEnCache(CalculadoraTests.java:27)
Actually, there were zero interactions with this mock.
El cual me indica que deberé de llamar a un elemento colaborador en cierto método, ¿que debo hacer? declararlo y llamarlo.
Una vez que ejecute esta prueba el resultado deberá ser satisfactorio.
Ahora bien, la lógica de como funciona la persistencia o bien en donde lo está haciendo de momento no me importa, pues sólo quiero corroborar que llame al método, eso es lo que me importa.
Para concluir con la lógica completa abarcaré un caso más, el cuál es entregar el resultado del caché en dado caso de que previamente se hubiese ejecutado, verificando que en dicho caso no persista la operación suma que estoy haciendo pues sería inútil y sin congruencia con la lógica que estoy queriendo hacer. Lo cual me quedaría algo así:
Obsérverse que hemos agregado comportamiento aislado y controlado del colaborador, y como nota extra estamos dando pie a los elementos que necesitamos agregar en nuestra implementación. Corriendo esto nos mandará los respectivo errores de compilación por que dichos métodos no existen. Sólo hay que agregarlos:
Una vez ejecutado obtenemos un resultado interesante:
Wanted but not invoked:
cacheDeCalculadora.existeElResultadoDeSumar(
10,
4
);
-> at com.makingdevs.CalculadoraTests.pruebaSumaDeDosNumerosObteniendoResultadoDeCache(CalculadoraTests.java:38)
However, there were other interactions with this mock:
-> at com.makingdevs.Calculadora.suma(Calculadora.java:8)>
Para hacer que esta prueba pase tendremos que hacer dicha validación, lo cual quedaría como lo siguiente:
Ejecutando esta prueba habremos de tener todo corriendo de manera satisfactoria. Y aunque no es de importancia para este caso como es que se va a guardar el caché tengo la certeza de que dicho método en esta lógica serña llamado en los casos en los que no exista dicho componente.
Me gustaría que el lector pudiera extrapolar este funcionamiento con DAO's u otros métodos de servicios que colaboran en la lógica de algún componente importante de su aplicación, en donde tenemos que desarrollar la lógica más elaborada para resolver un problema, pasar por ciertas condicionantes o bien saber que ciertos componentes deberán ser invocados en cierta cantidad de veces, o bien, no deberían llamarse, como ha sido este caso.
Sin lugar a dudas, hay muchas más cosas que mockito puede hacer para ayudarnos a determinar los comportamientos que ciertos componentes de nuestra aplicación debeierán tener, sin embargo, les recomiendo le echen un ojo al sitio de Mockito y a la documentación.
Para mejor referencia les dejo el repositorio de código en mi cuenta de github para que lo descarguen y puedan probarlo por ustedes mismos. Las ejecuciones s epueden hacer usando gradle o maven. Y también les recomiendo un artículo escrito por Alfredo Chávez en el sitio de Artesano.de/software, muy recomendable: TDD Cómo y porqué: Una guía para los no iniciados
Espero que les sirva tanto como a mi me sirvió.
Saludos a todos
- neodevelop's blog
- Inicie sesión o regístrese para enviar comentarios
Buen post
Buen post
GANADOR
Este post fue uno de los 3 ganadores, felicidades, tienes pase para SGCE2013.
En los DM's de Tuister me dijeron:
@Chrix2:
oye mano de tu post javamexico.org/blogs/neodevel… yo te recomendaria
1.- cambiar los asserttrue por assertequals ya que te da mejor feedback
y 2.- no hacer tus pruebas unitarias siempre con los mismos numeros, puedes usar un generador de numeros
asi son checks mas efectivos ;-)
@neodevelop:
En cuanto este en una compu, hago el refactor de mi prueba, pero explícame plis tu recomendación, me interesa!
@Chrix2:
Cuando tu haces unit tests q implican operaciones matematicas es mejor assertequals debido a q te indica mejor cuando sucede una situacion
Además cuando operas con flotantes puedes indicarle la precision. En caso de requerir una validacion con cierto tipo de margen. Y finalmente puedes trabajar con complejos.
El punto 2. Si tu pasas siempre con los mismos numeros una prueba es un tanto truqueada ya q solo verifica ese valor. Aqui lo recomendable es usar un generador de numeros o un oraculo q podría ser por ejemplo un tabla con valores de cálculos ya definidos q vas pasándole al test de forma aleatoria así tu tests esta más complejo. Por cierto el oraculo y el generador. Los usas en el setup del tests.
Cheers!
Como Agregar com.makingdevs
Hola, por lo que voy a decir me declaro NOVATO en esto de la programación y mi duda es: como
agregar --com.makingdevs-- a mi IDE, no sé por ejemplo netbeans 7.4 segun yo
baje un .jar de esta pagina para agregarla a mi IDE
*http://code.google.com/p/mockito/downloads/list*
pero no me funciono
solo funciono con el de JUnit al agregar el .jar a mi IDE
estos dos errores que me marcaban al inicio
import static org.junit.Assert.*;
import org.junit.Test;
se corrigieron me gustaria saber si hay una solicion para mi duda (que para muchos sera muy tonta) ya que me gusto mucho el tema de TDD
Gracias
Y saludos