Herramientas para hacer pruebas hay muchas en la mayoría de los lenguajes de programación. En el mundo Java una de las primeras herramientas diponibles y que aún sigue usándose ampliameten es JUnit, luego vinieron otras como JUnit 4 o TestNG. Spock es la siguiente herramienta que está empezando a usarse en muchos proyectos. El objetivo de Spock no es distinto del de Junit o TestNG pero la forma de definir las pruebas y el uso de Groovy le hacen tener una serie de características muy interesantes:
- Fácil de aprender: en sus ideas es muy parecido a JUnit y por tanto si conocemos JUnit podremos enpezar rápicamente con Spock.
- Groovy: es el lenguaje empleado en los casos de prueba, un lenguaje menos «verboso» que Java que produce código más legible y compacto que este. Algunas de sus características le hacen más adecuado que Java para los casos de prueba.
- Información: en caso de que alguna prueba falle proporciona información detallada de la comprobación que fallo y sus valores.
- Soporta varias orientaciones: Se adapta a como deseemos abordar las pruebas, probar primero, probar al final, teses unitarios, teses de integración, desarrollo orientado por pruebas, desarrollo orientado por comportamiento.
- Compatible: Es compatible con JUnit y por tanto se puede integrar con las principales herramientas e IDEs existentes.
import spock.lang.Specification class HelloWorldSpock extends Specification { def "length of Spock's and his friends' names"() { expect: name.size() == length where: name | length "Spock" | 5 "Kirk" | 4 "Scotty" | 6 } }
En este ejemplo se puede ver el DSL (Domain Specific Language) empleado por Spock. La sección expect es el test que queremos realizar, en este caso que una cadena tenga cierta longitud. La sección where son los datos que proporcionamos al test. Este test es un test parametrizado donde la sección expect se evalua para cada conjunto de valores indicado en la sección where.
El test anterior es muy simple y normalmente necesitaremos hacer mocks para los objetos con los que se relaciona el SUT (Subject Under Test) que queremos probar. En el siguiente ejemplo tenemos la interfaz de un servicio que se encarga de buscar datos en un sistema externo como una base de datos o un web service, si no queremos hacer un test de integración con ese sistema externo tendremos que hacer un mock de la interfaz Servicio. También tenemos la clase Procesador que se encarga de realizar una tarea sobre los datos que obtiene de un objeto que implemente la interfaz Servicio. La clase Procesador y su método procesar forma el SUT en este ejemplo.
Para hacer el mock de la interfaz en el ejemplo se usa la librería Mockito que nos permitirá definir el comportamiento que queramos de los mocks. En la sección setup es lo que hacemos, se crea el mock de Servicio, se define que cuando se llame al método findDatos del Servicio se devuelvan unos datos en concreto y se crea el Procesador pasándole la referencia al mock del Servicio para que le devuelva los datos que deseemos. Dado que conocemos los datos que le va a devolver el Servicio al Procesador y lo que debería hacer el Procesador conocemos el resutlado que debería devover el método procesar y esto es la comprobación que se realiza en la sección then donde se comprueba lo esperado con lo que devuelve. La sección when es el estímulo que proporcionamos al SUT con la tarea que queremos que haga y que nos servirá para comprobar que devuelve los resultados esperados. Este ejemplo es muy simple pero muestra los principales conceptos con los que realizar la mayoría de teses unitarios que queramos.
import spock.lang.Specification import org.mockito.Mockito class MockitoSpock extends Specification { def "procesado de elementos"() { setup: def servicio = Mockito.mock(Servicio.class) Mockito.when(servicio.findDatos()).thenReturn([[id:1, letra:'A'], [id:2, letra:'B']]) def procesador = new Procesador(servicio) when: def resultado = procesador.procesar() then: resultado == '1,2' } } interface Servicio { List findDatos() } class Procesador { private Servicio servicio Procesador(Servicio servicio) { this.servicio = servicio } String procesar() { def datos = servicio.findDatos() def ids = datos.collect { it.id } return ids.join(',') } }
En la siguientes capturas se puede ver el informe generado por un proyecto gradle que contenga estos teses tanto pasando de forma correcta como incorrecta.
Refencia:
http://code.google.com/p/spock/
http://code.google.com/p/spock/wiki/GettingStarted
http://code.google.com/p/mockito/