Páginas

viernes, 20 de julio de 2012

Pruebas unitarias con Spock y Mockito

Java
Si estamos convencidos de que las teorías de Extreme Programming y demás metodologías ágiles son útiles probablemente haremos algún tipo de prueba automatizada. Los inicios sueles ser difíciles ya que al principio hacer pruebas automatizadas parece más trabajo a realizar, sin embargo, una vez empezado se obtienen rápidamente una serie de beneficios en forma de errores detectados de forma temprana de manera que producimos software con menos errores, tenemos mayor seguridad de que entregamos algo que funciona y el hecho de vernos obligagos a desarrollar código testable obtenemos a cambio software de mayor calidad.

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.
Veamos un ejemplo de como son los teses con Spock:

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/