Spock vs JUnit
JUnit es, de facto, el framework para testear código Java. No creo que nadie le retire su trono a corto plazo, JUnit Lambda está cerca de ser liberado, por lo que las nuevas funcionalidades de Java 8 serán perfectamente soportadas, cosa que en los dos últimos años ha sido un ligero hándicap.
Sin embargo, recientemente está irrumpiendo con relativa fuerza un framework, que inicialmente se ideó para testear código en lenguage Groovy, pero dado que Groovy corre sobre la JVM se puede utilizar igualmente para testear código Java.
Spock
Spock es el acrónimo de “Specification and Mock”. Sabiendo esto es fácil deducir lo que nos permite hacer: crear especificaciones de nuestros sistemas, añadiendo capacidades para generar Mocks de dependencias. Digo especificaciones y no tests, cosa que puede resultar algo confusa de inicio, pero que se entenderá bien con los ejemplos. En líneas generales, una especificación no es más que una clase de test vitaminada.
Mi intención en este artículo es mostrar con un ejemplo claro en qué se diferencia Spock de JUnit. No quiero entrar en excesivos detalles de todo lo que Spock ofrece, para eso ya tenéis la documentación oficial.
La clase a testear
Vamos a crear tests para la una clase Calculator
, clase que ya utilizamos en el post sobre Mutation Testing. La versión “final”, de la que queremos generar tests, es esta:
Las funcionalidades de esta clase (que no es un derroche de buenas prácticas, por otro lado) son claras.
Tests en JUnit
Esta sería la clase de Test en JUnit que cubriría convenientemente todas las funcionalidades de Calculator
:
Vemos varios problemas aquí:
- Estamos añadiendo dos librerías encima de JUnit para mejorar nuestros tests: Fest assertions, para dar mayor expresividad a nuestros asserts (Harmcrest habría sido una alternativa igualmente válida), y Mockito para crear Mocks de dependencias
- Para utilizar Data Driven Testing de forma adecuada necesitamos que nuestros tests sean ejecutados por el runner
Parameterized
en lugar del runner JUnit por defecto - La cantidad de código boilerplate que hay que escribir fuera de nuestros métodos de tests es grande, ya que necesitamos preparar la tabla de datos de una forma algo fea (en el método
data
), necesitamos un métodosetUp
para inicializar dependencias, un constructor para inicializar los datos de cada test, etc
Todos estos contrapuntos no son malos en sí, pero ya sabemos que Java nunca se ha caracterizado por ser demasiado conciso. Esa verbosidad puede ser muy buena en determinadas ocasiones, ya que fuerza buenas prácticas y pensar de manera adecuada en nuestros diseños. Pero a la hora de escribir tests, ¿no sería mejor ahorrarse estos pequeños inconvenientes?
Spock al rescate
Vamos a sumergirnos de lleno en Spock viendo cómo sería la implementación final de nuestra especificación para la clase Calculator
:
Vemos que:
- El test está escrito en Groovy
- El nombre de la clase de test tiene el sufijo
Spec
y extiende a la claseSpecification
. El nombre especificación viene de que nuestra clase no solamente está testeando código, también está generando especificaciones legibles por un ser humano - Podemos crear un Mock de una clase invocando al método
Mock
, que forma parte del framework - La anotación
Subject
nos indica cuál es el sujeto que estamos especificando - Nuestros métodos pueden ser nombrados con una cadena de caracteres libre, sin seguir ningún tipo de convención impuesta por lenguajes de programación. Esto hace mucho más fácil describir qué vamos a testear sin temor a crear nombres de métodos demasiado largos e ilegibles
- Los métodos de test están divididos en secciones claras, siguiendo el patrón Arrange Act Assert, o “Given When Then”. En este ejemplo, al ser tan sencillo, no hay necesidad de preparar nada en la sección
given:
, por lo que la omitimos - Cada sección puede tener su descripción, resumiendo qué estamos preparando, ejecutando o verificando
- La sección
then:
contiene assertions que será evaluadas utilizando Groovy Truth. No hay necesidad por tanto de utilizar métodos assert, tan solo expresiones booleanas - Las verificaciones de los mocks son muy claras:
1 * audit.register("2 + 2 (ABSOLUTE)")
verifica que estamos llamando una sola vez al métodoregister
deaudit
con esos parámetros, y fallará en caso contrario. Simular comportamientos en los Mocks es muy sencillo también, para profundizar nada mejor que la documentación oficial - Las tablas de datos para utilizar Data Driven Testing son extremadamente claras, según podemos ver en las secciones
where:
. Nada de crear arrays de dos dimensiones, y campos en la clase para soportarlo
Reseñar por último que la forma en que el framework muestra errores en assertions es muy expresiva, indicando de forma gráfica donde está el error, los valores de todas la variables implicadas, etc. Aquí tenemos un ejemplo:
Spock vs JUnit
No pretendo convencer a nadie de si un framework es mejor que el otro, tan sólo comparar las diferencias entre ambos. Los beneficios de usar Spock son claros, pero también es cierto que obliga a añadir un lenguage nuevo a nuetro stack tecnológico, y eso no siempre es bueno, desde el punto de vista de la mantenibilidad, el aprendizaje por parte del equipo, etc.
Además, utilizar Spock en un proyecto Maven hace necesario configurar nuestro fichero POM de manera adecuada, saltándonos ligeramente las convenciones (más información aquí). Si utilizamos Gradle es bastante más fácil.
Por último, el hecho de que este año se vayan a publicar dos libros sobre la materia (aquí tenéis uno y el otro) da una idea de la difusión que Spock está teniendo dentro de la comunidad.
(El código, como siempre, en GitHub).