No, no vamos a hablar de cómo implementar pruebas haciendo pair programming con ‘El capitán Spock’ del universo Star Trek… Comenzamos una serie de posts en los que presentamos un framework, nada nuevo, para implementar pruebas orientadas a BDD de una forma un poco diferente.

En el post de hoy haremos una introducción a la implementación de especificaciones de tests con Spock y algunos ejemplos.

Pongámonos en contexto...

Spock framework

Spock es una especificación y framework para la implementación de pruebas orientadas a BDD en proyectos JAVA o Groovy. La característica más importante es la forma de escribir su especificación de tests que lo hacen muy expresivo y, junto con Groovy como lenguaje de programación, también altamente expresivo y amigable, hacen que este framework sea muy atractivo y fácil de usar.

Es compatible con la mayoría de IDE’s y herramientas de integración continua gracias a que está basado en JUnit.

% block:blockquote
% items:
% text:Más documentación:
% endblock

“Spock is a testing and a specification framework for Java and Groovy applications”

BDD

Behavior Driven Development (BDD) surge como refinamiento de TDD (Test Driven Development) y es la forma de unir la parte técnica con la parte de negocio.

Partiendo de las historias de usuario definidas por negocio (“Como [rol], quiero [acción], para [objetivo]”), el siguiente paso es definir unos criterios de aceptación basados en escenarios con el formato (“Dado…, Cuando…, Entonces…”). Estos criterios de aceptación nos servirían para definir las pruebas y, ayudados de un framework que permita esta especificación, poder automatizarlas.

De esta forma, tenemos un lenguaje común entendible por tres perfiles: negocio, QA y desarrollo; clave para el entendimiento entre las partes más importantes a la hora de definir la funcionalidad y las pruebas y su automatización para el posterior desarrollo de dichas funcionalidades.

Así, el orden de actuación que tenemos cuando aplicamos BDD es el siguiente:

Groovy

Groovy es un lenguaje de programación dinámico basado en Java que se ejecuta sobre la JVM. Tiene una sintaxis sencilla y amigable, parecido al lenguaje natural, de forma que se elimina toda la parte innecesaria reduciendo así el número de líneas de código y simplificando el desarrollo.

Además, permite el tipado dinámico y la compilación en tiempo de ejecución con las opciones de tipado y compilado estático si lo necesitas.

Como funcionales adicionales permite la metaprogramación en tiempo de ejecución y compilación, DSL’s, programación de scripts...

% block:blockquote
% items:
% text:Más documentación:
% endblock

¿Cómo construimos una especificación BDD con Spock?

Para definir una especificación de test sobre una historia de usuario con Spock haremos lo siguiente:

Definir una clase Groovy.
Incluir un import de spock.lang.
Y añadir una serie de elementos que pueden ser necesarios:

*Nota**:** Si setting o el cleanup es común a varios features, debe ir en un fixture method en vez de en un bloque.

Bloques

La combinación de ambos nos va a permitir ejecutar una condición (bloque expect), en un solo test con diferentes combinaciones de datos (bloque where) sin necesidad de tener una definición de test por cada combinación. Además, podemos analizar el resultado de la ejecución de este tipo de test de una forma muy explicativa gracias a la anotación @Unroll que veremos más adelante.

Condiciones

Las condiciones, del tipo de las aserciones en JUnit, serán sentencias simples, sin la necesidad de añadir la palabra ‘assert’ que serán evaluadas según la “verdad en Groovy”.

Specification as Documentation

Además de poder poner literales en el título de un ‘feature method’, podemos ponerlos en cada uno de los bloques y utilizar un bloque auxiliar ‘and’ que puede ser combinado con cualquiera de los anteriores para encadenar sentencias y darle más detalle a nuestros tests.

Ejemplo de especification con Spock

Veamos ahora un ejemplo de especificación con Spock en el que creamos un objeto customer y probamos la inserción y eliminado de dicho objeto en una lista.

En dicho ejemplo vemos el uso de @Shared, los bloques Given/When/Then, el método cleanupSpec() para vaciar la lista al final de la ejecución de todos los tests y algunas funcionalidades extras de Groovy.

groovy

class CustomerExampleSpec extends Specification{
   // sin @Shared
   def customer = null
   def customerData = [name:"Darth",lastName:"Vader"]
   // Con @Shared
   @Shared customersList = []
   def "Add new customer to list successfully"(){
       given: "customer data"
           println "Customer complete name is: ${customerData.name} ${customerData.lastName}"
       when: "a new customer is created"
           customer = new Customer(customerData)
       and: "it’s added to list"
           customersList << customer
       then: "the list contains the new customer"
           customer in customersList
   }
   def "Remove an existing customer from list successfully"(){
       given: "existing customer"
           // la variable customer vuelve a ser nula al estar anotada con
           //@Shared
           // la lista customerList mantiene el objeto insertado en el test anterior al
           // estar anotada con @Shared
           customer = customersList.find{c ->
               c.name == customerData.name && c.lastName == customerData.lastName
           }
       when: "remove from list"
           customersList.remove(customer)
       then: "the list not contains the customer"
           !(customer in customersList)
   }
   def cleanSpec(){
       // vacíamos la lista al final de todos los tests
       customersList = []
   }
}

Otros puntos importantes

Error Logs

La combinación de Spock y Groovy nos permiten ver de una forma sencilla en los logs, por qué han fallado nuestros test mostrando los valores de nuestras variables en cada momento. Veamos un ejemplo:

Condition not satisfied:
customer.name == "Patricia" //fails
| | |
| | false
| \[Fatima\]
\[Customer\[id=1, name=Fatima, lastName=Casau\]
Expected :Patricia
Actual :Fatima

Unroll

En un data-driven-test, gracias a la anotación @unroll, podemos saber en qué ejecución ha fallado nuestro test, bien por el orden, o bien porque nos permite sustituir variables en el título de nuestros test. Esta etiqueta es utilizada es especificaciones con bloques ‘expect/where’.

Ejemplo:

TEST

groovy

@Unroll
def """maximum of two numbers (with data table) using @unroll with variables:
       The maximum between x:--md-var-hashtag-x and y:--md-var-hashtag-y is --md-var-hashtag-result"""() {
   expect:
       Math.max(x, y) == result
   where:
       x  | y  || result
       1  | 3  || 3
       7  | 4  || 4 // fails
       0  | 1  || 1
}

LOG

maximum of two numbers (with data table) using @unroll with variables: The maximum between x:7 and y:4 is 4
Condition not satisfied:
Math.max(x, y) == result
 | | | | |
 7 7 4 | 4
 false

Extensiones

Tenemos a disposición una serie de extensiones que nos darán mucho juego a la hora de programar nuestros tests.

Conclusión

Como vemos, Spock, junto con Groovy, nos permiten definir nuestras especificaciones BDD e implementar los tests asociados de una forma sencilla de forma que, además, también sirven de documentación de nuestro sistema y con un lenguaje que pueda ser entendido por perfiles no técnicos.

Por otro lado, nos permite tener en un mismo sitio la definición y la ejecución, y no mantener diferentes recursos para este propósito.

Veremos más utilidades de Spock en próximos capítulos ;)

Cuéntanos qué te parece.

Enviar.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.