Tests funcionales con Spock y Geb para una aplicación Spring Boot

0
5788

Este tutorial refleja lo que he ido aprendiendo por el camino y ofrece una serie de ejemplos que espero le sean útiles a quien también quiera sumergirse en los tests funcionales con Spock y Geb por primera vez.

Índice de contenidos

1. Introducción

Hace poco tuve que introducir tests funcionales en el proyecto Java en el que trabajo y elegí Spock + Geb para enredar un poco. ¿Y por qué esta combinación? Primero elegí Spock porque tras una estupenda charla en Codemotion de Iván López (@ilopmar) tenía ganas de probarlo. Tras indagar un poco vi que Geb ofrece una estupenda integración con Spock, tira del WebDriver de Selenium e implementa el patrón página como buscaba. Win-win.

Este tutorial refleja precisamente lo que he ido aprendiendo por el camino y ofrece una serie de ejemplos que espero le sean útiles a quien también quiera sumergirse en los tests funcionales con Spock y Geb por primera vez. ¡Vamos a ello!

2. Entorno

Para este tutorial he trabajado en el siguiente entorno:

  • Spring Boot 1.3.1

  • Java 8

  • Groovy 2.4

  • Spock 1.0

  • Geb 0.12.2

  • Maven 3.3.3

3. Dependencias y descripción del proyecto

Lo primero que necesitamos es saber qué dependencias debemos incluir en nuestro fichero pom.xml si queremos usar Spock y Geb en los tests de nuestra aplicación.

Para usar Spock necesitamos incluir tanto el core en sí como, en este caso, la librería que nos permite trabajar con Spock y Spring. Además, necesitamos incluir la librería con la versión de Groovy que queramos utilizar.

Por otra parte, Spock sólo puede crear mocks para interfaces Java así que si queremos mockear clases concretas con Spock como hacemos con Mockito necesitaremos dos librerías más. En nuestro ejemplo no utilizaremos mocks, pero podéis encontrar varios ejemplos de tests con los diferentes tipos de dobles. Dejo aquí estas dependencias también para que veáis el pack completo:

pom.xml

Además de estas dependencias, necesitamos añadir un plugin para poder compilar el código de Groovy:

pom.xml

Para usar Geb necesitamos la librería core de Geb y si vamos a utilizarlo con Spock, la librería que permite esta interacción. Además necesitamos el WebDriver de Selenium y los drivers de los navegadores que queramos utilizar:

pom.xml

Con esto ya tenemos listo el pom 🙂

Como aplicación de ejemplo, utilizaremos una pequeña web que permite añadir y listar una serie de productos (libros, discos y películas). Esta aplicación está hecha con Spring Boot usando Freemarker para las plantillas de las páginas HTML. Para simplificar las cosas, la parte de persistencia utiliza una base de datos en memoria H2. El código de este ejemplo lo tenéis en este repositorio de GitHub.

4. Tests con Spock

Spock es un framework para el testeo de aplicaciones Groovy que también puede utilizarse para tests en Java. El propósito del tutorial no es revisar Spock en sí sino ver su uso con Geb, pero para aquellos que no lo conozcan aquí va un vistazo rápido a su sintaxis.

Siguiendo la documentación oficial, Spock nos permite escribir especificaciones (specifications) que describen características (features) esperadas y que son exhibidas por un sistema de interés. Este sistema, que puede ser desde una sola clase hasta toda la aplicación, se denomina sistema bajo especificación (system under specification (SUS)). Una especificación se compone principalmente de fixture methods para la configuración y limpieza del entorno (como los @Before y @After de JUnit) y feature methods que son los tests que queremos ejecutar.

Para crear una clase de test tenemos que crear una clase Groovy que extienda la clase Specification propia de Spock. Esta clase además le indica a JUnit que debe ejecutar la especificación con Sputnik, el runner JUnit de Spock.

ProductsSpec.groovy

Cada test (o feature method) se nombra usando un literal que puede contener cualquier carácter, lo que aporta una mayor claridad a la hora de ejecutar y revisar el resultado de múltiples tests. Vemos también que el test sigue la estructura Given-When-Then típica del desarrollo BDD, y que cada bloque puede acompañarse con un literal que describe su comportamiento. ¿Y echas de menos los assert en este tests? ¡Pues no lo hagas! Todas las sentencias de un bloque then son asserts implícitos, así que nos evitamos escribir el assert. También se pueden tener bloques setup al principio del test (a parte de los métodos de setup que se puedan tener a nivel de especificación) así como bloques cleanup al final. Para ilustrar esto, el ejemplo cuenta con un bloque cleanup que se encarga de eliminar los cambios hechos en la base de datos, aunque podría hacerse este rollback con la anotación @Transactional de Spring.

Cuando un bloque tiene varias sentencias o queremos marcar de forma explícita las distintas acciones, podemos dividirlo usando la etiqueta and: como en el siguiente ejemplo (¡spoilers! El ejemplo contiene conceptos de puntos posteriores 😛 ):

ProductsGebSpec.groovy

Otra característica interesante de Spock es su forma de reportar los fallos, que hace mediante los llamados power asserts. Estos no sólo dan la típica diferencia entre el valor esperado y el obtenido, sino que también dan los valores intermedios de la expresión evaluada. Los power asserts se desarrollaron inicialmente en Spock y después se incorporaron al core de Groovy.

Spock failure report

5. Patrón página

Para poder hacer tests funcionales necesitamos acceder y operar con los elementos de la página, sus estilos, etc. Si hacemos todo esto directamente en cada test es fácil que acabemos con un caos curioso de elementos HTML y estilos CSS, todo bien repetidito por cada vez que queramos tirar de los mismos elementos. Si a esto le sumamos la gran susceptibilidad a cambio que tienen estos tipos de test…​ vamos, que tener que mantener algo así no se lo deseo yo ni a mi peor enemigo. Por suerte tenemos el patrón página para poner un poco de orden.

El patrón página encapsula todo lo referente a la estructura HTML de la página, con lo que reduce la duplicidad de código y, si la interfaz cambia, sólo esa clase se verá afectada. Además, esto nos permite centrarnos a la hora de hacer los tests en los servicios ofrecidos por la página en vez de en los detalles de implementación y sus mecanismos.

En nuestro ejemplo tenemos dos páginas: una más sencilla para el listado de productos y un formulario para introducir nuevos productos que requiere un poquito más de complejidad. Veamos primero el caso de la página listado. Esta, además del listado de productos, tiene un botón que nos lleva al formulario de nuevo producto. Con Geb para implementar el patrón página extendemos la clase Page en la que se define el contenido de la página mediante un DSL. Nuestra página de listado de productos queda entonces así:

ProductsPage.groovy

La variable url indica la dirección en la que se encuentra la página. Podemos usar direcciones relativas como en el ejemplo si definimos la URL base en el fichero de configuración.
at es la condición que se ha de cumplir para poder confirmar que estamos en la página adecuada, algo muy utilizado en los tests funcionales para ver si se está siguiendo el flujo esperado.
Por último tenemos el content, donde definimos los distintos elementos de la página que queremos utilizar en nuestros tests.

La página correspondiente al formulario tiene algún elemento más. Este sería el código:

FormPage.groovy

Además de la cabecera que nos permite en el test comprobar que estamos en la página correcta (con at), tenemos los campos de entrada del formulario, varios botones y la parte de gestión de errores. Sobre lo campos de entrada lo único a destacar es el campo category, que corresponde al desplegable para la categoría del productos, y para el que se puede utilizar el módulo Select de Geb que ya implementa los accesos al elemento seleccionado y demás. Por último tenemos la parte de gestión de errores del formulario. Para poder instanciar una página a un objeto patrón página han de estar presentes todos los elementos definidos en la clase. Como los errores no siempre van a aparecer en el formulario (afortunadamente) necesitamos indicar que ese elemento es opcional (required:false) para que la página pueda instanciarse sin problemas. Además, podemos capturar errores concretos mediante un filtro sobre la lista completa de errores.

Vemos que también podemos tener helper methods como es en este caso el método fillForm que se encarga de rellenar los campos del formulario y de hacer el submit. Esto nos permite evitar duplicidad de código en los tests y desacoplarlos más de la implementación concreta del formulario.

6. Test funcionales

Esto del patrón página está muy bien, pero lo que queremos es ver todo el montaje en acción. Pues bien, a la hora de hacer un test funcional tenemos varias opciones dependiendo de si nos apoyamos sólo en Spock o también en Geb, de si queremos reporte de fallos o no, etc. Vamos a ver algunas de estas opciones en detalle.

Si el test extiende directamente la clase Specification de Spock, debemos crear nosotros el navegador:

ProductsSpec.groovy

Este pequeño test crea el navegador, va a la dirección “/products” y comprueba la página a la que llegamos contiene un heading concreto. Pero aquí estamos acoplando el test a la implementación específica de mi página. ¿Cómo podemos evitar esto? Pues con el patrón página, ¡claro! El mismo ejemplo quedaría así usando la página ProductsPage:

ProductsSpec.groovy

El método to navega a la URL definida en ProductsPage y comprueba que estamos en la página esperada mediante la condición especificada en el at de ProductsPage, todo en uno. Si la condición del at falla, el método to fallará. Además, como en este caso el when y el then ocurren en la misma sentencia podemos usar en su lugar el bloque expect que equivale precisamente a eso (un when y un then en la misma sentencia). Si quisiéramos comprobar en algún punto del test que estamos en la página que esperamos, podemos utilizar el método at que comprueba que se cumple la condición de at de la página, como veremos en ejemplos posteriores.

¿Y las anotaciones de la clase de test? La primera, @SpringApplicationConfiguration, configura el contexto de spring a cargar en los tests para arrancar correctamente la aplicación. @WebIntegrationTest hace que la aplicación se arranque antes de cada clase de test y se detenga una vez ejecutados todos sus tests, algo que puede ser útil cuando estamos desarrollando un test pero que podríamos preferir no hacer si hemos de ejecutar múltiples clases de test. En ese caso, levantaríamos la aplicación de forma independiente antes de los tests, ejecutaríamos todos los tests funcionales contra esa instancia de la aplicación, y después pararíamos la aplicación. Para ello eliminaríamos las dos anotaciones puesto que el test ya no gestionaría el arranque de la aplicación.

En este ejemplo hemos usado directamente la clase Specification de Spock, pero si vamos a utilizar Geb podemos usar la subclase que viene en la integración de Geb con Spock: GebSpec. Esta clase configura una instancia de Browser que se llama desde todos los métodos, con lo que no hace falta ni crear la instancia ni hacer referencia a ella ya que se hace implícitamente:

ProductsGebSpec.groovy

Para navegar a una página basta con utilizar directamente el método to y lo mismo con at. Para interactuar con un elemento de la página, como en este caso con el botón de “nuevo producto”, podemos utilizarlo directamente. Así, newButton.click() equivaldría a browser.page.newButton.click(), ya que por delegación la sentencia se envía al browser y a su página actual. En el repositorio de github podéis ver distintos ejemplos de tests, copio aquí algunos ejemplos para comentarlos:

ProductsGebSpec.groovy

Este test accede al formulario de creación de producto, llama al método fillForm (que rellena los campos correctamente y selecciona el botón de guardar), y entonces comprueba que el navegador regresa a la página del listado como marca nuestro flujo de navegación y que el número de productos del repositorio se incrementa en 1. Esto último lo hace mediante el método old() de Groovy, que se guarda el valor inicial de una sentencia y nos permite compararlo con su valor final. Como vemos los tests siguen siendo compatibles con Spring y podemos inyectar nuestro repositorio sin problemas.

¿Y para comprobar que los errores se gestionan correctamente? Pues aquí tenemos otro ejemplo:

ProductsGebSpec.groovy

En este caso tenemos un formulario que guardamos sin haber rellenado el campo de nombre. Aquí podemos utilizar el mayNotBeEmptyError que hemos definido en _FormPage_y comprobar que seguimos en la página del formulario y que el error está presente. También comprobamos que el nuevo producto no se ha guardado y que por tanto el repositorio tiene los mismos elementos que al principio del test.

La peculiaridad de este test es que tenemos que limpiar la cache al final. Geb crea una única instancia del navegador para todos los tests a modo de optimización. Como en este caso forzamos que falle la validación del formulario el test termina en esa página y los campos rellenados mantienen su valor en el navegador para el siguiente test. Esto puede darnos problemas si en el siguiente test empezamos justo en el mismo formulario (al hacer el to FormPage el navegador no recarga porque ya está ahí) ya que partiremos con unos valores no contemplados. Al invocar el método ClearCache() forzamos que Geb destruya la instancia actual del navegador y levante una nueva.

Por último, también podemos hacer tests sobre los estilos CSS de la página:

ProductsGebSpec.groovy

En este test añadimos un producto al repositorio, vamos al listado de productos y comprobamos que la cabecera de la tabla tiene los estilos correctos.

6.1. Reporte de fallos

Geb incorpora una sencilla herramienta de reporte de fallos que toma una captura del estado del navegador cuando un test falla. Esta utilidad se incluye también en las diferentes integraciones de Geb con JUnit, Spock y TestNG en forma de superclase lista para usar. En el caso de Spock, esta superclase es GebReportingSpec y en nuestro test lo que hacemos es extender esta clase en vez de la habitual GebSpec:

ProductsReportingSpec.groovy

Las capturas de pantallas se guardan dentro del propio proyecto pero tenemos que indicarle el directorio concreto ya que no tiene ninguno por defecto. Para ello basta con darle valor a la propiedad reportsDir del fichero de configuración de Geb como veremos en la siguiente sección.

6.2. Tests fuertemente tipados

Una alternativa a cómo hemos implementado el patrón página es crear los objetos fuertemente tipados. Veamos cómo queda la página del formulario siguiendo este otro enfoque:

FormTypedPage.groovy

Vemos que ahora indicamos la página a la que deberían llevarnos los distintos botones, o las posibles páginas como en el caso del save (al listado de productos si la validación es correcta o de nuevo al formulario si ésta falla). Además, hemos añadido una serie de métodos que nos permite abstraer aún más los tests de las particularidades de la página (como de qué campos hay que rellenar o la comprobación de si estos están vacíos o no), así como métodos para activar los botones que nos devuelven el tipo de página esperado u Object si esa acción puede llevar a distintas páginas según el caso (como pasa con save). Todo esto hace los tests menos verbosos y más centrado en las acciones que se quieren llevar a cabo, y si usamos un IDE nos permite recuperar el autocompletado y las alertas de tipo. Pero para muestra, un botón:

ProductsTypedGebSpec.groovy

La página a la que enviamos el navegador la almacenamos en una variable del tipo de página correspondiente (FormTypedPage), llamamos a su método de rellenado de campos, y hacemos click en el botón guardar. El resultado de esa acción es la nueva página a la que se redirige el navegador (que como no hay errores de validación debería ser del tipo ProductsTypedPage). Ahora la comprobación del test se produce al instanciar con éxito esta página, sin necesidad de realizar la llamada explícita a at.

Y el test de comprobación de la gestión de errores quedaría:

ProductsTypedGebSpec.groovy

De nuevo desaparece la llamada explícita at que se sustituye por la instanciación de la página, y la existencia de errores se comprueba a través del método de FormTypedPage.

7. Configuración de Geb

Los parámetros de configuración de Geb se recogen todos en el script GebConfig.groovy, fichero que debemos crear en la raíz del proyecto para que Geb lo reconozca automáticamente. Aquí podemos indicar tanto la URL base a utilizar, como el fichero para las capturas del reporte de fallos, como el driver que queremos utilizar para el browser. Esto último, si bien para utilizar Firefox basta con importar la librería y crear una nueva instancia, se complica un poco más para usar Chrome. En este caso hay que descargarse el propio driver e indicar en la configuración la ruta al ejecutable descargado. En cualquier caso, necesitamos incluir también las dependencias necesarias en el pom.xml.

GebConfig.groovy

Como bonus, se pueden definir diferentes environments en el fichero de configuración para definir distintos navegadores y seleccionar el que se quiere utilizar mediante una variable de entorno en la llamada de ejecución.

8. Conclusión

Me ha gustado trabajar con Spock. Es my sencillo, genera tests muy legibles y está totalmente en línea con el BDD. Y Geb y su patrón página también me han ganado. Los tests funcionales quedan claros y desacoplados de la estructura y diseño de las páginas HTML, lo que mejora su mantenimiento y permite centrarse en el comportamiento de la página y su flujo esperado. La claridad aumenta aún más si además implementamos los objetos del patrón página como fuertemente tipados. Lo único que he visto menos claro es que al no tener comprobación explícita de la página en la que se está, el éxito del test se evalúa a través de la posible instanciación o no de la página, lo que nos obliga a crear variables únicamente para esta comprobación y rompe un poco la legibilidad del then. En cualquier caso, mi valoración del combo Spock + Geb para tests funcionales en Java es altamente positiva.

9. Referencias

Dejo algunos enlaces que me han resultado de utilidad a la hora de aprender sobre Spock, Geb y los tests funcionales en general, y de los que he sacado ideas para este tutorial:

Dejar respuesta

Please enter your comment!
Please enter your name here