Test unitarios con fechas en Java

Introducción

En el artículo anterior hablé de forma general de como comencé a crear test unitarios en Java.
En este quiero hablar en específico de crear test unitarios cuando el código a testear utiliza fechas.

Manejar fechas en Java es complicado (al menos hasta la versión 7) pero es incluso peor el tema cuando intentas crear test unitarios para tu código.

El problema principal de manejar fechas en Java es debido a que la clase Date, que es la que se suele utilizar habitualmente, es mutable. Es decir, que se puede modificar su valor interno después de haber sido inicializada.
El otro problema es el como crear test unitario con fechas si utiliza como referencia la fecha actual del sistema.

Contexto

Como en el artículo anterior he utilizado en el código las librerias Junit v4 y harmcrest. Podéis encontrarlas en:

http://junit.org/
http://hamcrest.org/JavaHamcrest/

Mutable?¿

Te preguntarás cual es el problema que los objectos de tipo Date sean mutables.
El tema es que podemos modificar el campo fecha de un objeto sin que este pueda hacer nada.

Vamos a verlo en un ejemplo. Tenemos un clase User con un campo con la fecha de nacimiento de tipo Date:

En el siguiente código podemos ver en un test unitario cómo puede afectar la mutabilidad a la clase User:

Hemos cambiado el año de la fecha de nacimiento del usuario de 1990 a 2014 sin que el objeto user lo sepa.

Formas de probar código con fechas

Hay principalmente tres formas de crear métodos testeables que contengan fechas:

  • 1. Pasar la fecha actual como parámetro. De esta forma resulta muy fácil el cambiar la fecha actual del sistema y así probar todos los posible casos que queramos.
  • 2. Obtener la fecha actual por un método que podamos cambiar. Dentro del método que queremos probar, obtenemos la fecha actual del sistema por un método (en la misma clase) con el scope protected para que en el test unitario podamos sobrescribir el método utilizando una clase que extienda ese método.
  • 3. Utilizar nuestra propia clase como fecha del sistema. Dentro del método que queremos probar, utilizamos una clase propia para obtener una instancia de la fecha actual del sistema.

Creo que la mejor forma de ver como funcionan estas tres formas de probar código con fechas es utilizando el mismo problema y ver como es el código en las tres aproximaciones.

El problema: ¿es mi cumpleaños hoy?

Tenemos la misma clase User que utilizamos al principio del artículo:

y queremos probar el método isBirthday (no está implementado en el código anterior porque es diferente según la aproximación). Esté método comprueba si el usuario cumple años hoy y devuelve un boolean con el resultado.

Si para obtener la fecha actual del sistema utilizamos:

new Date()

El código no sería testable porque no podríamos cambiarla.
Nota: Esto es solo un ejemplo de como probar código con fechas. Personalmente no suelo probar cada método de forma individual sino que pruebo el comportamiento del sistema.

Primera aproximación: pasar la fecha actual como parámetro

Primeramente veremos la estructura del método a probar y posteriormente el código de los test unitarios. Así el método isBirthday sería como aparece a continuación:

La fecha actual (now) se ha pasado como parámetro, luego se comprueba los dias y meses de los objetos now y dayOfBirthday de tipo Date (se ha omitido esa parte por innecesaria en el ejemplo) y posteriormente se devolverá true o false según estas comprobaciones.

A continuación dos test unitarios que comprueban los dos casos básicos del método, que el usuario cumple años hoy y que no cumple años hoy:

Las variables five_july_2000, six_july_2010 y five_july_2010 son constantes de tipo Date que como no aportan nada al ejemplo he eliminado su definición.

 

Segunda aproximación: obtener la fecha actual con un método que podamos cambiar

Seguimos con el mismo problema pero como obtenemos la fecha actual de un método no necesitamos pasarlo como parámetro. Así, el código del código de producción es:

Los mismos dos test unitarios de la aproximación anterior son implementados en el siguiente código con el nuevo método:

Lo que estamos haciendo en ambos test unitarios es crear una clase anónima que extiende de la clase User, donde está sobrescrito el método getDate con la fecha actual que deseamos.

 

Tercera y última aproximación: Nuestra propia clase fecha del sistema

Esta tercera forma de probar código con fechas la leí en el libro Test Driven de Lasse Koskela y es la que suelo utilizar habitualmente.
Podéis encontrar el código fuente de esta junto con todo el código fuente del libro en la siguiente dirección:

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

El código de producción sería:

La clase SystemTime que hemos utilizado para obtener la fecha actual, es parte de esta tercera aproximación, y consiste en una clase que genera la fecha actual a partir del valor en milisegundo de una instancia hija de la interfaz TimeSource:

que por defecto tiene el valor System.currentTimeMillis() pero que puede ser modificada si le pasamos a SystemTime otra instance hija que implemente la interfaz TimeSource.

El código que nos interesa de la clase SystemTime es:

Así los dos test unitarios quedarían de la siguiente forma:

Como puedes comprobar aunque los test quedan bastante simples, pueden afectar al comportamiento de otros test unitarios que utilicen la clase SistemTime para obtener la fecha actual ya que no será la real.

Para solucionarlo tenemos que asignar a SystemTime su valor por defecto. Para ello, en el método que se ejecutar después de cada test unitario lo añadimos:

 

Ejemplo de donde utilizar estos métodos

Hay un caso en particular que me está viniendo muy bien el poder cambiar la fecha del sistema, y es cuando pruebo procesos en segundo plano.

En general los procesos en segundo plano siempre tienen un campo de tipo Date que la forma de probar que funciona es manualmente: esperar hasta cierto día/hora en concreto para comprobar que ha funcionado como debía.
Si podemos crear test unitarios cambiando la fecha del sistema tendremos más seguridad que nuestro proceso funciona.
Esto no quita que se pueda probar manualmente que el proceso funciona.

 

Conclusiones

Aunque personalmente la aproximación que más me gusta es la última creo que mientras el código sea testeable cualquiera vale.

Modificado 08-01-2017: Cambiado formateo del código a GitHub

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

Por qué cambié de opinión de crear mi propio blog

La idea que tuve sobre como comenzar en el blog no tenía mala pinta. Consistía en crear mi propio blog como primera entrada en mi blog. Es decir, que mi primera entrada sería sobre como he desarrollado paso a paso mi propio blog.

Lo primero que hice fue elegir la tecnología que iba a utilizar y las funcionalidades mínimas que iba a tener el blog.
Como funcionalidades mínimas serían el crear, editar y mostrar artículos.
Y las tecnologías que iba a utilizar como entorno de trabajo eran:

  • IDE: Eclipse porque es el que suelo utilizar habitualmente.
  • Repositorio para el código: Git en GitHub.
  • Gestor de paquetes: Apache Maven.
  • Servidor Web: Google app engine.
  • Base de datos: Google app engine nos provee de base de datos.

Haciendo cálculos, me iba a costar un mes el crear el blog.

Cambio de opinión

Al día siguiente, después de haber terminado de configurar el entorno de trabajo, llegué al trabajo muy contento y con ganas de hablar con mis compañeros sobre el blog.
Después de contarles mi idea junto con todos los detalles un compañero me pregunta:
– “¿Por qué has utilizado Maven?”
Yo pensé en ese momento que era una pregunta un poco tonta pero me acordé que mi compañero no es programador de Java y que seguramente no conoce Maven. Así que le expliqué que es Maven y para que se utiliza. Él me contestó:
– “Sé que es Maven pero ¿por qué lo vas a utilizar?”
La verdad es que me quedé en blanco y no supe que contestarle.

Por qué Maven

Esa misma tarde estuve pensando sobre el porque utilizar Maven y realmente no tenía un motivo sólido para utilizarlo:
  • Facilitaría el trabajo en el futuro cuando cambiara de versión alguna librería que utilizase.
  • Crear versiones de mi blog.
  • Automatizar procesos manuales que tuviera que hacer para subir nuevas versiones de mi blog.
La única razón que podría utilizar para usar Maven era la última pero utilizando Google App Engine sólo tengo que pulsar un botón para subir una nueva versión de mi blog, por lo que no tengo que realizar ningún proceso manual.
En mi opinión, estas razones no eran suficientes para añadir un nivel de dificultad, como es Maven, a mi Blog. Así que lo que estaba haciendo era utilizar Maven porque es lo normal. Lo estándar en caso de aplicaciones Java.
En definitiva, decidí no utilizar Maven.

Por qué crear un Blog

Empecé a cuestionarme todo lo que estaba utilizando para crear el Blog, hasta que llegué a la siguiente pregunta:
– ¿Tengo qué crear un blog como primera entrada en mi blog?
Había gastado una tarde en configurar todo el entorno e iba a estar alrededor de un mes en crear un blog con mínimas funcionalidades cuando hay muchísimas opciones gratis o por poco dinero en Internet.
Estoy empezando a leer libros sobre metodologías Agile y Lean y lo que estaba haciendo era lo menos ágil que podría hacer. Me había puesto a mi mismo un impedimiento para comenzar con el blog que me iba a costar al menos un mes.
– ¿Qué es lo que realmente quería? 
Lo que quería era crear un blog e ir escribiendo poco a poco artículos sobre cosas que me gusten y si en un futuro quería crear mi propio blog pues fenomenal pero no hacerlo algo que tenía que hacerlo si o si.

Eligiendo Blog

Así que llegué a la conclusión que no era necesario el crear un blog y empecé a escribir una lista con los requisitos que debía tener mi blog:
  • Fácil de utilizar: No quería gastar mucho tiempo en aprender como utilizar el blog. Los informáticos también nos merecemos cosas fáciles de usar.
  • Fácil de cambiar el diseño: Comenzar con un diseño básico y luego poco a poco mejorarlo
  • Sin necesidad de instalar nada: No quería crear mi propio servidor ni instalar nada en mi portátil.
  • Fácil de exportar las entradas del blog: De este modo si cometía un error al elegir el Blog podría cambiar a otro.
  • No gastar dinero: Para comenzar de la forma más barata posible.
Con esta lista en la mano empecé a buscar posibles opciones y la mayoría de blogs tenían todos los requisitos que estaba buscando.
Así que entre los dos más grandes, que son Blogger y WordPress, elegí Blogger porque la opción gratis de WordPress tiene publicidad y no permite utilizar mi propio dominio (www.enrique-martin.com)

Conclusiones

Es importante el saber el porque hacemos algo. Cual es la esencia del porque lo hacemos.
De este este modo podremos saber qué es lo realmente importante y dejar lo demás como opcional.
En mi caso, lo que realmente quería era crear un blog de la forma más simple y rápido posible.
No quiero decir que es una mala idea el crear un blog y escribir un artículo sobre ello.
Lo que quiero decir es que si se sabe distinguir lo esencial de lo no esencial puedes tomar decisiones fácilmente empezando con algo básico e ir poco a poco mejorándolo.