1.- ¿Qué es?

Spock es un framework de testing y especificación para aplicaciones Groovy y Java. Está basado en JUnit, RSpec, Mockito, etc, etc.


2.- ¿Por qué?

Para nosotros porque es el que viene por defecto con Grails. En general porque es un framework de testing muy expresivo, que puede servir incluso de documentación para gente con poco background técnico.


3.- ¿Qué pinta tiene?


import spock.lang.Specification


class FirstSpockTest extends Specification {

def “let’s test the size method”(){

expect:

name.size() == length

where:

name     | length

“Yoda”   | 4

“Luke”   | 4

“Obiwan” | 6

}

}


Esto sería un test muy simple escrito con Spock. Para que la cosa funcione la clase de test tiene que extender de spock.lang.Specification. Dentro de esta clase podemos definir un método por cada feature que queramos probar. El método se puede llamar como queramos, de hecho el nombre del método es un string (“”) en el que podemos poner cualquier cosa.

Dentro de este método tenemos dos bloques, expect y where.

Como podemos suponer en expect expresamos una expectativa (la condición que queremos que se cumpla), mientra que en el where pondremos los valores que queremos testear.


Es bastante simple pero en este ejemplo faltan bastantes cosas. Nos sirve para probar un método de una variable pero no es muy real, vamos a poner algo un poco más “de verdad”.


import spock.lang.Specification


class FirstSpockTest extends Specification {

def “retrieve the first person”(){

setup:

PeopleService peopleService = new PeopleService()

when:

person = peopleService.getFirstPerson(nombre)

then:

person.name != null

person.class = Person.class

where:

nombre << [“Han”, null]

}

}


Esto ya tiene una estructura un poco más similar a algo que una persona normal podría entender. Aquí tenemos tres bloques, setup, when y then.

En el bloque setup definiremos los objetos necesarios para poder lanzar el test,

el bloque when es donde se lanza la acción a testear y el bloque then es donde indicamos las expectativas a cumplir.

En este último bloque vemos que no hay ni assertions. No hace falta ponerlas ya que van implícitas, si no se cumple lo que hay en la parte de las expectativas el test no pasa.


Si lanzamos el test del ejemplo veremos que para el valor null el test falla, y nos aparece el error de la siguiente manera:

Condition not satisfied:


person.name != null

|      |    |

|      null false

Person@6affe94b


Spock nos muestra el error de una forma bastante clara, señala la clase del objeto que da el error, el campo que falla con su valor y la condición que falla. De esta manera es bastante fácil corregir el error.


En el caso de que la condición fuera algo demasiado complicado, podríamos utilizar un método helper para simplificar el código en el bloque then:


import spock.lang.Specification


class FirstSpockTest extends Specification {

def “retrieve the first person”(){

given:

PeopleService peopleService = new PeopleService()

when:

person = peopleService.getFirstPerson(nombre)

then:

checkCondition(person)

where:

nombre << [“Han”, null]

}


def checkCondition(person){

assert person.name != null

assert person.class = Person.class

}

}


En este caso si que tendríamos que utilizar explicitamente el método assert para que en caso de error nos aparezca correctamente la notificación del error.


Condition not satisfied:


checkCondition(person)

|              |

null           Person@7efd4978


Condition not satisfied:


person.name != null

|      |    |

|      null false

Person@34bc6a08


4.- Profundizando

Con lo que hemos visto hasta ahora tenemos un test con una o dos features pero por lo general una clase de test puede tener bastantes features a probar. Spock nos provee de algunos métodos “especiales” que podemos utilizar en nuestros tests para facilitarnos un poco la vida.

Los principales son setup, cleanup, setupSpec y cleanupSpec.

El método setup se ejecuta antes de ejecutar cada una de las features, y nos puede servir para inicializar cosas comunes a todas ellas.

El método cleanup se lanza después de ejecutar cada una de las features, y podemos utilizarlo para dejar la base de datos limpia, borrar ficheros o cerrar conexiones.

El método setupSpec se ejecuta antes de lanzar la primera feature y podemos usarlo para inicializar objetos comunes a todas las features por ejemplo.

Finalmente el método cleanupSpec se lanza después de haber ejecutado todas las features y puede ser usado para limpiar datos u objetos que puedan haber creado las features.


Cuando estamos desarrollando suele ser habitual que no queramos que una feature se lance o que queramos lanzar sólo una en concreto. Para esto tenemos dos anotaciones: @Ignore e @IgnoreRest.

Lo que hace @Ignore es ignorar la feature que esté marcada con esta anotación, mientras que @IgnoreRest lo que hace es ignorar todas las features que no lleven esta anotación.


5.- Mocking

Spock nos permite mockear objetos de una forma más o menos sencilla.


def "let's mock"(){

   given:

       def peopleService = new PeopleServiceImpl()

       def person = Mock(Person)

   when:

       peopleService.greet(person)

   then:

       1 * person.receive("Hello")

}


Por ejemplo en este test de una feature hemos creado un Mock del objeto Person y nos aseguramos de que cuando llamemos al método greet de peopleService, el objeto persona responde a una llamada al método receive con el parámetro “Hello”.

En este caso nuestra expectativa es que se llame al método receive con el parámetro “Hello” una sola vez, pero se pueden especificar comportamientos más abiertos.

Por ejemplo si cambiaramos el bloque then por:

(1.._) * person.receive(!null)

Lo que queremos comprobar es que person recibe al menos una llamada al método receive con un parámetro distinto de null.


De esta manera podemos comprobar que nuestros objetos se comporta de la forma deseada, sin tener que preocuparnos de los valores que reciben o no.


Nota: por defecto Spock nos permite mockear interfaces, si queremos mockear también clases deberemos incluir en el classpath la librería cglib-nodep.


6.- Stubbing

De la misma manera que podemos mockear un objeto, también podemos stubbearlo, esto es modificar su comportamiento en un momento dado. Esto es muy útil para simular la respuesta de servicios externos.


def "now let's stub"(){

   given:

       def person = Stub(Person)

       person.receive("message1") >> "OK"

       person.receive("message2") >> {

           println "We are screwed!"

           "FAIL"

       }

   when:

       def response = person.receive("message1")

       def response2 = person.receive("message2")

   then:

       response == "OK"

       response2 == "FAIL"

}


En este ejemplo podemos ver cómo hemos modificado el comportamiento del método receive de la clase Person, para que en caso de recibir el parámetro “message1” nos devuelva la cadena “OK” y para que en caso de recibir “message2” imprima por la consola un mensaje de error y devuelva el mensaje “FAIL”.


7.- Spock y Grails

Desde la versión 2.3 de Grails, Spock ha dejado de ser un plugin independiente y ha pasado a ser el framework de testing por defecto de Grails.

En Grails los tests están en el directorio tests (O_O”). La nomenclatura de una clases de tests suele|debe ser “NombreClaseATestearSpec. Estas clases deben extender de spock.lang.Specification igual que cualquier otro test de Spock.

A parte de las funcionalidades de Spock que hemos descrito, Grails añade algunas cosas nuevas para facilitar el testeo de las aplicationes.


7.1- @TestFor

Esta anotación se utiliza para indicarle a la clase del test la clase que vamos a probar. En caso de que vayamos a probar un controlador añadirá a nuestra clase de test una variable del tipo del controllador llamada controller, a través de la cual podremos llamar a los distintos métodos o acciones del controlador. En caso de que queramos testear un servicio nos creará una variable service, etc, etc.


7.2- Mocks

Grails añade algunos métodos de mock a parte de los que podamos tener por defecto con Spock. Por ejemplo mockDomain nos permite que se añadan a un objeto de dominio los métodos dinámicos que se añaden al iniciar la aplicación Grails (findBy, save, …)


7.3- Objetos HTTP

Grails añade además los objetos típicos que se usan en las llamadas HTTP, un objeto request, un objeto response y un objeto params.


8.- Referencias

Tags: Grails Testing

Publicado por Rubén el 16/01/2014 a las 15:40



Publicar comentario: