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