Testing orientado a BDD con Spock (1/2)

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 GroovyLa 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.

Más documentación:

“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:

  • Definición de historias de usuario.
  • Definición de criterios de aceptación.
  • Implementación y automatización de pruebas.

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…

Más documentación:

¿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:
    • Fields:
      • Con @Shared → Cuyo valor es compartido entre distintos Features. Se comporta como un static final.
      • Sin @Shared → No son compartidos entre distintos Features. Se comporta como un final.
    • Fixture Methods: Se utilizan para settear o limpiar el entorno de los test que se van a ejecutar en una especificación. No son obligatorios:
      • def setupSpec() {} → Se ejecuta antes de cada uno de los ‘feature methods’.
      • def setup() {} → Se ejecuta antes del primer ‘feature methods’.
      • def cleanup() {} → Se ejecuta después de cada uno de los ‘feature methods’.
      • def cleanupSpec() {} → Se ejecuta después del último de los ‘feature methods’.
      • En setupSpec() y cleanupSpec() no se pueden referenciar ‘fields’ que no estén anotados con @shared.
    • Feature Methods: Son los métodos que se van a utilizar para probar las ‘features’ de nuestro sistema. Se pueden nombrar con ‘literales’, lo que nos permite una mejor descripción de estos con un lenguaje natural en el idioma que queramos. Normalmente, un ‘feature method’ consta de 4 bloques:
      • Setting de variables* “Given/Setup”.
      • Un estímulo “When/Where”.
      • La comprobación de la respuesta al estímulo “Then/Expect”.
      • Cleanup de variables* “Cleanup”.

*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

  • Setup/Given: Se utilizan para realizar cualquier tarea necesaria para el funcionamiento de un ‘feature method’. No puede estar precedido por ningún otro bloque ni estar repetido. Given, simplemente es como un ‘alias’ de ‘setup’ para que el test sea más legible siguiendo el formato given-when-then.
  • When/Then: Siempre van juntos y se pueden repetir cuantas veces se quieran. El bloque ‘when representa un estímulo y pueden contener cualquier tipo de sentencias. Por otro lado, el bloque ‘then representa el resultado esperado al estímulo y solo puede contener condiciones, excepciones, definición de variables…
  • Expect/Where: Además de los bloques que hemos visto anteriormente, tenemos dos bloques más:
    • Expect → Similar a ‘then’, se utiliza normalmente en combinación con ‘where’. En él se suele definir un estímulo y su respuesta en un sola línea.
    • Where → Va al final de un test y no se puede repetir. Se utiliza para definir ‘data-driven tests’.

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.

  • Cleanup: Data Driven Test. Se utiliza para resetear o liberar cualquier recurso utilizado en un test. No se puede repetir y solo puede ir seguido de un bloque ‘where’. Además, se ejecuta al margen de que el test lance una excepción de forma que, se pueden resetear o liberar los recursos que se hayan quedado ‘sucios’ debido a la excepción.

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.

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

@Unroll
def """maximum of two numbers (with data table) using @unroll with variables:
       The maximum between x:#x and y:#y is #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 ;)

Ingeniero de Software desde 2007 (universidades, e-commerce, prensa digital, banca…). Scrum Master en Paradigma Digital desde 2013 PSM I Certified, Scrum.org. Ponente en diversos eventos sobre TI (Conferencia Agile Spain, Agile Open Space, Greach, SpringIO, SpringOne, T3chFest, Madrid Groovy User Group, Madrid Spring User Group). Organizadora de 'Madrid Spring User Group'.

Ver toda la actividad de Fátima Casaú

Escribe un comentario