Mis primeros pasos con test unitarios en Java

Definición

Un test es una prueba que se realiza sobre el código de forma automática. Y la mejor definición que he encontrado sobre qué es, o mejor dicho qué no es, un test unitario es de Michael Feather (@mfeathers):

Un test no es unitario cuando:

  • Habla con la base de datos
  • Se comunica a través de la red
  • Toca el sistema de ficheros
  • No puede ejecutarse al mismo tiempo que tus otros test unitarios
  • Tienes que hacer cosas especiales para ejecutarlo

Después de esta definición, para mí un test unitario es un test que se ejecuta en pocos milisegundos ya que en definitiva el listado anterior lo que hace es describir los casos que hacen que el test tarde en ejecutarse.

 

Cómo funciona

Ejecutas el test unitario en tu IDE favorito (Eclipse, Netbeans, IntelliJ) y si tu código pasa el test vas a ver el nombre de tu test en verde, y si no lo pasa será rojo.

 

Qué utilizo

Para crear test en Java utilizo las librerias JUnit 4  y Harmcrest.

JUnit es el estandar para crear test en Java y no conozco ninguna otra alternativa. Utilizo la versión 4 porque permite identificar los test utilizando anotaciones Java: @Test.

Harmcrest lo utilizo porque hace que se entienda mejor el test y cuando el test está en rojo los mensajes de error son más claros, pero no es necesario utilizarlo para escribir un test.

 

Ejemplo

La primera línea es una anotación de JUnit 4 que identifica el método que aparece después de la anotación como un test.

A continuación aparece un método que no devuelve ningún valor y cuyo nombre describe lo que comprueba el test. El nombre del método es muy importante porque sirve como documentación para el programador.

El contenido del método está escrito utilizando la libreria Harmcrest. El método assertThat tiene dos parámetro. En el primero, ponemos lo que queremos comprobar y en el segundo, el valor que debe tener.
Los dos objetos deben de ser del mismo tipo, por ejemplo, en este caso son dos String.

 

Por qué escribir test

En mi caso empecé a escribir test unitarios por básicamente dos motivos:
  1. TDD: Quería probarlo y hacer test unitarios me parecía el primer paso.
  2. Menos testing manual: No me gusta hacer pruebas manuales y todo lo que pueda reducir el tiempo que le dedico a ello pues mejor que mejor. No quiero decir que no las haga, sino simplemente que no me gustan.
Al ir escribiendo test descubrí otros dos motivos que son incluso mejores que los dos anteriores:
  1. Seguridad/Confianza al cambiar el código: Principalmente al cambiar el código escrito hace un tiempo. No se cuantas veces en el pasado habré cambiado un método de una clase para solucionar un error y posteriormente me dí cuenta que añadí errores en otro lado del código. Con test unitarios si rompo algo el test me avisará.
  2. Documentación para programadores: Los test unitarios se convierten en documentación y cuanto mejor son los nombres de los test, y el test en sí, mejor es la documentación. Si necesito utilizar un método y no sé como funciona suelo mirar los test unitarios de este para averiguarlo.

 

Al meollo: Como comencé

Cuando comenzé a escribir test unitarios me dí cuenta que no sabía como escribirlos en muchos casos. Por ejemplo:

  • Servlets
  • JSP
  • Comunicación con internet
  • Comunicación con la base de datos
  • Comunicación con el sistema de ficheros
  • Fecha y hora del sistema

En muchos de estos casos si consigo crear un test no sería unitario porque como vimos en la definición harían que el test tardara en ejecutarse. Además que tendría que configurarlos de alguna forma para hacer que se comportaran como quiero.

Estuve pensando sobre estas limitaciones y como no supe como solucionarlo llegué a la conclusión que lo mejor que podía hacer era evitar el problema: aislar la lógica de mi sistema en métodos que no accedieran a nada de lo anteriormente citado y que tuvieran unos valores muy claros de entrada y salida.

Si, por ejemplo tenemos una función que accede a base de datos para obtener una lista de usuarios y luego los agrupa por departamentos creaba un método cuya entrada fuera la lista de usuarios y cuya salida es la lista de departamentos:

Para el tema específico de las fechas, lo solucioné pasando siempre la fecha actual como parámetro de entrada del método.

Así empecé a crear test unitarios de funciones relativas a fecha o que tuvieran una entrada y salida bien definidas.

Nota: sé que la mejor forma de verlo es por ejemplos. Crearé un proyecto en GitHub junto con al menos un post explicando el código.

 

Problemas que he encontrado

Han sido dos los problemas principales que he encontrado y ya los he comentado en el punto anterior aunque de forma específica:

  1. Probar código en tecnología propia de Java: JSP, Servlets, Hibernate, JDBC y otros frameworks.
  2. Probar código que se comunica con recursos: Base de datos, internet y sistema de ficheros.

Las soluciones al primer punto están en la primera parte del libro Test Driven de Lasse Koskela (@lassekoskela):

http://www.manning.com/koskela/

Además, en el libro se dan las claves para practicar TDD y ATDD. Así que hay que a comprarlo.

Para el segundo punto tengo tres palabras que suelen solucionar la mayoría de estos problemas:

Inyección de Dependencias

Siempre que una clase utilice métodos de otra pasa un objeto de esta como parámetro. Por ejemplo:

De esta forma podemos crear un doble (test double) de UserDAO para que se comporte como queramos, y pasárselo a ServiceUser.

Si no inyectas dependencias en tus clases te vas a dar cuenta que tienes un gran problema con todo el código que has hecho anteriormente ya que no es testeable…
Voy a escribir sobre este tema en el futuro pero si no puedes esperar echa un vistazo al screencast de Sandro Mancuso (@sandromancuso):

http://craftedsw.blogspot.nl/2012/12/screencast-testing-and-refactoring.html

Es oro puro. Explica como convertir código no testable en testable y añadirle test sin romper nada.

Conclusiones

Aunque tiene sus dificultades el escribir test unitarios, es muy fácil empezar y los beneficios son muchos.

Links

Modificado 08-01-2017: Cambio formateador de código a GitHub

  • kikers25

    Es cierto que todavía me queda cosas por mirar pero estos "primeros pasos con test unitarios" los hice hace ya unos meses. Quiero explicar cómo está siendo mi evolución.

    Ahora suelo practicar TDD siempre, bueno, hasta que me encuentro con código sin test. Entonces intento utilizar lo que comenta Sandro Mancuso en su video.

    La definición que pones de test unitario mucha gente la toma como buena pero no me gusta porque no me permite cambiar el código.

    Por ejemplo, si tengo un clase "Conversion" (con test unitarios) que convierte una cantidad de dinero de una moneda a otra y quiero crear clases para cada tipo de conversión de moneda, entonces, ¿no sería un test unitario porque prueba más de una clase?

    Limitar un test unitario diciendo que prueba una clase no me gusta porque creo que lo que realmente estamos probando es el comportamiento de nuestro código en un caso específico, da igual si es una o varias clases.

    No quería decir que no hay que probar nuestras clases junto con los dispositivos externos como el sistema de ficheros o la base de datos, lo único que digo es que entonces no es un test unitario sino que es un test de integración. Los test de integración no son precisamente rápidos en ejecutarse.

    Cuando tienes muchos test unitarios en un proyecto es muy importante la velocidad de ejecución de estos porque tienes que ejecutarlos a menudo si no pierda su utilidad. En mi caso estoy trabajando en un proyecto con casi 500 test unitarios (que creo que no es mucho comparado con otros proyectos), suelo ejecutarlos todos a menudo y tardan unos 4-5 segundos. Si tardaran mucho en ejecutarse no lo haría a menudo. Además tengo un plugin de Eclipse que se llama Infinitest que cada vez que salvo me ejecuta los test relacionados con el código salvado.

    Personalmente los test unitarios lo que hacen es darme feedback de mi código y si no es en un ciclo corto porque los test tardan mucho en ejecutarse no me merece la pena.

    El tema de la base de datos es muy muy interesante y muy complejo. Aunque es cierto que puede haber errores al acceder/modificar la base de datos creo que la mayoría de ellos están antes o después de acceder a esta por lo que tienen menos importancia.

    En mi caso, estoy trabajando en un proyecto que utiliza Google App Engine y lo mejor que tiene es que es muy fácil crear test de tus DAOs.

    Suelo utilizar mocks, fundamentalmente JMock que me ayuda a mejorar el diseño ya que obliga a escribir todas las llamadas a los dobles de test.

    Me apunto el mirarme Spock y Cucumber. Groovy ya lo tenía puesto el ojo. ¿Has probado BDD? ¿Te ha gustado más que TDD?

    La forma de resolver el tema de las fechas no es la mejor opción pero al menos es un comienzo. Quería escribir un artículo sobre todo el tema de fechas y test unitarios en Java y como ha mejorado con Joda time, que es una maravilla.

    Nota: Vaya pedazo de comentarios que escribes 🙂

  • Andrés Viedma

    Es que si no escribo tochacos no soy yo (aunque sea en los comentarios) xD

    Lo de las fechas ojo, en Java 8 han cambiado, inspirándose precisamente en Joda (supongo que te referías a eso, pero por si acaso).

    Lo que dices de no probar una única clase tienes razón, la verdad, pero es importante en cualquier caso entenderlo como una prueba de "una unidad mínima", aunque no sea una clase. Se pueden hacer pruebas de integración y hasta de aceptación muy rápidas, no creo que sea ese el tema. Lo del tiempo sigo sin estar de acuerdo, es deseable que sean muy rápidos, pero no es algo que distinga el que la prueba sea unitaria, en mi opinión.

    Y BDD no lo he probado… por eso te digo que lo pruebes tú 😉 En cualquier caso, personalmente no me gusta usar TDD (pero es algo muy personal).

    Me apunto lo del Infinitest, no lo conocía 😀

  • Pingback: Injección de dependencias en Java para dummies – Enrique Martín()