113
SPOCK SPOCK Pruebas en Java con Groovy y Pruebas en Java con Groovy y Andrés Viedma Andrés Viedma

Tests en Java con Groovy y Spock

Embed Size (px)

DESCRIPTION

No hay forma, por más que pruebas bibliotecas Java de tests no encuentras una forma de hacerlos sencilla y potente y que realmente te convenza del todo. Tu código de pruebas a menudo acaba siendo un pequeño batiburrillo ilegible que prefieres tocar lo mínimo posible, y que incluso te quita las ganas de hacer más tests. Has oído que la gente de Groovy habla muy bien de Spock, pero no te fías mucho de esos paganos que hacen guarreos con el código y no adoran debidamente al gran dios J. En esta charla, que se presentó el 26/6/2014 organizada por MadridJUG y MadridGUG, Andrés Viedma nos mostrará lo sencillo que es integrar Spock en un proyecto Java, y cómo su gran expresividad y la potencia de Groovy nos pueden ayudar a crear tests en los que puedas preocuparte más de qué quieres probar que de cómo tienes que programar la prueba, y que además sirvan para documentar de una forma muy elegante cuál es el comportamiento esperado de nuestra querida aplicación Java. Se explorará además cómo Spock puede encajar perfectamente no solo con TDD sino también con la automatización de tests funcionales sobre la web, e incluso con técnicas de más alto nivel como BDD y metodologías ágiles en general.

Citation preview

Page 1: Tests en Java con Groovy y Spock

SPOCKSPOCK

Pruebas en Java con Groovy y Pruebas en Java con Groovy y

Andrés ViedmaAndrés Viedma

Page 2: Tests en Java con Groovy y Spock

¿Quién soy?¿Quién soy?

Dinosaurio del softwaremás de 20 años como profesional

Javero inquieto

Sospechoso habitual del MadridGUG y MadridJUG

Escribo en Apaga y vuelve a encenderhttp://apagayvuelveaencender.blogspot.com

Andrés ViedmaAndrés Viedma@andres_viedma@andres_viedma

Page 3: Tests en Java con Groovy y Spock

Pero... ¿de verdad hacemos Pero... ¿de verdad hacemos pruebas?pruebas?

Page 4: Tests en Java con Groovy y Spock

EL CASTIGADOR DE LOS TESTSEL CASTIGADOR DE LOS TESTS

Page 5: Tests en Java con Groovy y Spock

EL CASTIGADOR DE LOS TESTSEL CASTIGADOR DE LOS TESTS

Da su merecido (o sea, pruebas) a todas las líneas de código

No hace excepciones

Page 6: Tests en Java con Groovy y Spock

ROBIN HOOD, EL INFALIBLEROBIN HOOD, EL INFALIBLE

Page 7: Tests en Java con Groovy y Spock

ROBIN HOOD, EL INFALIBLEROBIN HOOD, EL INFALIBLE

Nunca falla un tiro.Ni tampoco falla en el código.

Las pruebas son para los torpes

Page 8: Tests en Java con Groovy y Spock

EL INCREIBLE PINOCHOEL INCREIBLE PINOCHO

Page 9: Tests en Java con Groovy y Spock

EL INCREIBLE PINOCHOEL INCREIBLE PINOCHO

Hace muchíiiiisimas pruebas.No se lo cree ni él.

Page 10: Tests en Java con Groovy y Spock

EL INFORMÁTICO VAGOEL INFORMÁTICO VAGO

Page 11: Tests en Java con Groovy y Spock

EL INFORMÁTICO VAGOEL INFORMÁTICO VAGO

Hacer pruebas esimportante.

Page 12: Tests en Java con Groovy y Spock

EL INFORMÁTICO VAGOEL INFORMÁTICO VAGO

Hacer pruebas esimportante.

Es una pena quetambién sea

UN COÑAZO

Page 13: Tests en Java con Groovy y Spock

¿Y TÚ?...¿Y TÚ?...

Page 14: Tests en Java con Groovy y Spock

Tests a prueba de VagosTests a prueba de Vagos

Subir nivel de abstracción

No “programar tests” → declarar casos de prueba

Sencillez + potencia

Expresividad → test es a la vez documentación

Fácil de ejecutar en sistemas de integración continua y en IDEs

Información que facilite la detección de errores

Page 15: Tests en Java con Groovy y Spock

Mirando SPOCKMirando SPOCK

Page 16: Tests en Java con Groovy y Spock

SPOCKSPOCK

Hecho en Groovy

Page 17: Tests en Java con Groovy y Spock

SPOCKSPOCK

Hecho en Groovy

Page 18: Tests en Java con Groovy y Spock

SPOCKSPOCK

Muy parecido a Java (“extensión” del lenguaje)

Compatible con él (se ejecuta en JVM)

Lenguaje dinámico (o no)

Mucho “azúcar sintáctico”

Mucha “magia negra”

Diseñado para maximizar sencillez y expresividad

Tiene su propio runner JUnit

Hecho en Groovy

Page 19: Tests en Java con Groovy y Spock

¡Uf! Para montar esovoy a necesitar...

Page 20: Tests en Java con Groovy y Spock

¡Uf! Para montar esovoy a necesitar...

Page 21: Tests en Java con Groovy y Spock

<plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <compilerId>groovy-eclipse-compiler</compilerId> </configuration> <dependencies> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-eclipse-compiler</artifactId> <version>2.8.0-01</version> </dependency> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-eclipse-batch</artifactId> <version>2.1.8-01</version> </dependency> </dependencies></plugin>

1. Compilar código Groovy1. Compilar código Groovy

Page 22: Tests en Java con Groovy y Spock

<plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <compilerId>groovy-eclipse-compiler</compilerId> </configuration> <dependencies> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-eclipse-compiler</artifactId> <version>2.8.0-01</version> </dependency> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-eclipse-batch</artifactId> <version>2.1.8-01</version> </dependency> </dependencies></plugin>

1. Compilar código Groovy1. Compilar código Groovy

Page 23: Tests en Java con Groovy y Spock

<plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <compilerId>groovy-eclipse-compiler</compilerId> </configuration> <dependencies> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-eclipse-compiler</artifactId> <version>2.8.0-01</version> </dependency> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-eclipse-batch</artifactId> <version>2.1.8-01</version> </dependency> </dependencies></plugin>

1. Compilar código Groovy1. Compilar código Groovy

Page 24: Tests en Java con Groovy y Spock

2. Dependencias con Spock2. Dependencias con Spock

<!-- Test dependencies --><dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>2.1.5</version> <scope>test</scope></dependency>

<dependency> <groupId>org.spockframework</groupId> <artifactId>spock-core</artifactId> <version>0.7-groovy-2.0</version> <scope>test</scope></dependency>

Page 25: Tests en Java con Groovy y Spock

3. Ejecutar tests *Spec (opc.)3. Ejecutar tests *Spec (opc.)

<!-- Surefire: include Spock tests (*Spec) --><plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.14</version> <configuration> <includes> <include>**/*Spec.java</include> <include>**/Test*.java</include> <include>**/*Test.java</include> <include>**/*TestCase.java</include> </includes> </configuration></plugin>

Page 26: Tests en Java con Groovy y Spock

3. Ejecutar tests *Spec (opc.)3. Ejecutar tests *Spec (opc.)

<!-- Surefire: include Spock tests (*Spec) --><plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.14</version> <configuration> <includes> <include>**/*Spec.java</include> <include>**/Test*.java</include> <include>**/*Test.java</include> <include>**/*TestCase.java</include> </includes> </configuration></plugin>

Page 27: Tests en Java con Groovy y Spock

Sólo dependenciasSólo dependencias

apply plugin: 'groovy'

// spocktestCompile 'org.codehaus.groovy:groovy-all:2.1.5'testCompile( group:'org.spockframework',name:'spock-core', version:'0.7-groovy-2.0')

Page 28: Tests en Java con Groovy y Spock

¿IDEs?¿IDEs?

Groovy Eclipse Plugin

– http://groovy.codehaus.org/Eclipse+Plugin

Versiones Eclipse entre 3.5 (Galileo) y 4.3 (Kepler)

Instalar versión adecuada (Extra Groovy compilers – 2.1)

Plugin Groovy incluido en instalación

Page 29: Tests en Java con Groovy y Spock

¿Nada más?¿Nada más?

¡Nada más!SDK Groovy no hace falta

Requisitos mínimos

JDK 5.0

Probado con Maven 2.0.9 (última 3.2.1...)

Eclipse Galileo

No requiere cambios importantes en entorno de desarrollo

Page 30: Tests en Java con Groovy y Spock

Mi primer test SPOCKMi primer test SPOCK

Page 31: Tests en Java con Groovy y Spock

import spock.lang.Specification;

class SillySpec extends Specification { def "add two numbers"() { expect: 1 + 1 == 2 }

}

El test más tonto del mundoEl test más tonto del mundo

src/test/groovy/SillySpec.groovy

Page 32: Tests en Java con Groovy y Spock

import spock.lang.Specification;

class SillySpec extends Specification { def "add two numbers"() { expect: 1 + 1 == 2 }

}

El test más tonto del mundoEl test más tonto del mundo

src/test/groovy/SillySpec.groovy

Page 33: Tests en Java con Groovy y Spock

import spock.lang.Specification;

class SillySpec extends Specification { def "add two numbers"() { expect: 1 + 1 == 2 }

}

El test más tonto del mundoEl test más tonto del mundo

“Assert” implícito

src/test/groovy/SillySpec.groovy

Page 34: Tests en Java con Groovy y Spock

El segundo test más tonto del mundoEl segundo test más tonto del mundo

def "add elements to a list"() { given: def list = ["one", "two"] when: list.add("three") list << “four” then: list == ["one", "two", "three", "four"] }

Page 35: Tests en Java con Groovy y Spock

El segundo test más tonto del mundoEl segundo test más tonto del mundo

def "add elements to a list"() { given: def list = ["one", "two"] when: list.add("three") list << “four” then: list == ["one", "two", "three", "four"] }

Page 36: Tests en Java con Groovy y Spock

El segundo test más tonto del mundoEl segundo test más tonto del mundo

def "add elements to a list"() { given: def list = ["one", "two"] when: list.add("three") list << “four” then: list == ["one", "two", "three", "four"] }

Page 37: Tests en Java con Groovy y Spock

El segundo test más tonto del mundoEl segundo test más tonto del mundo

def "add elements to a list"() { given: def list = ["one", "two"] when: list.add("three") list << “four” then: list == ["one", "two", "three", "four"] }

DSL

Page 38: Tests en Java con Groovy y Spock

El segundo test más tonto del mundoEl segundo test más tonto del mundo

def "add elements to a list"() { given: def list = ["one", "two"] when: list.add("three") list << “four” then: list == ["one", "two", "three", "four"] }

equals

Tipos opcionales Collection literals

; opcional

Page 39: Tests en Java con Groovy y Spock

Organización en BloquesOrganización en Bloques

given (setup)

when

then

expect

where

cleanup

Estímulo / respuesta

Comprobación directa

and: encadenar varios bloques del mismo

tipo

Page 40: Tests en Java con Groovy y Spock

Organización en BloquesOrganización en Bloques

given (setup)

when

then

expect

where

cleanup

Estímulo / respuesta

Comprobación directa

LegibilidadWhen/then: efectos lateralesExpect: método funcional puro

and: encadenar varios bloques del mismo

tipo

Page 41: Tests en Java con Groovy y Spock

Condiciones then / expectCondiciones then / expect

when:stack.push(elem)

then:!stack.emptystack.size() == 1stack.peek() == elem

Condiciones booleanas sencillas

when:stack.pop()

then:thrown(EmptyStackException)stack.empty

Condiciones excepciones thrown / notThrown

Interacciones (...)

Sólo pueden contener condiciones o definición de variables

Page 42: Tests en Java con Groovy y Spock

Ejecutando...Ejecutando...

Page 43: Tests en Java con Groovy y Spock

Ejecutando...Ejecutando...

Page 44: Tests en Java con Groovy y Spock

Ejecutando...Ejecutando...

Page 45: Tests en Java con Groovy y Spock

Tests como documentaciónTests como documentación

@Issue("http://www.mybugtracking.com/BUG-012324")

def "add elements to a list"() { given: "a list with elements” def list = ["one", "two"] when: "two more are added” list.add("three") list << “four” then: "the list includes now both elements” list == ["one", "two", "three", "four"] }

Page 46: Tests en Java con Groovy y Spock

Tests como documentaciónTests como documentación

@Issue("http://www.mybugtracking.com/BUG-012324")

def "add elements to a list"() { given: "a list with elements” def list = ["one", "two"] when: "two more are added” list.add("three") list << “four” then: "the list includes now both elements” list == ["one", "two", "three", "four"] }

Page 47: Tests en Java con Groovy y Spock

Tests como documentaciónTests como documentación

@Issue("http://www.mybugtracking.com/BUG-012324")

def "add elements to a list"() { given: "a list with elements” def list = ["one", "two"] when: "two more are added” list.add("three") list << “four” then: "the list includes now both elements” list == ["one", "two", "three", "four"] }

Comportamiento queda mejor documentado

Page 48: Tests en Java con Groovy y Spock

Tests como documentaciónTests como documentación

@Issue("http://www.mybugtracking.com/BUG-012324")

def "add elements to a list"() { given: "a list with elements” def list = ["one", "two"] when: "two more are added” list.add("three") list << “four” then: "the list includes now both elements” list == ["one", "two", "three", "four"] }

Comportamiento queda mejor documentado

Bueno para razonamiento TDD

Page 49: Tests en Java con Groovy y Spock

Cambio de estadoCambio de estado

def "generate a sequential identifier"() {

given: def gen = new SequentialIdGenerator() when: def id = gen.generateId() then: id == old(gen.nextId) gen.nextId == old(gen.nextId) + 1 }

Page 50: Tests en Java con Groovy y Spock

Cambio de estadoCambio de estado

def "generate a sequential identifier"() {

given: def gen = new SequentialIdGenerator() when: def id = gen.generateId() then: id == old(gen.nextId) gen.nextId == old(gen.nextId) + 1 }

Page 51: Tests en Java con Groovy y Spock

Cambio de estadoCambio de estado

def "generate a sequential identifier"() {

given: def gen = new SequentialIdGenerator() when: def id = gen.generateId() then: id == old(gen.nextId) gen.nextId == old(gen.nextId) + 1 }

Ojo: no usar si el resultado es un objeto mutable

Page 52: Tests en Java con Groovy y Spock

Matchers HamcrestMatchers Hamcrest

import static spock.util.matcher.HamcrestMatchers.closeTo

class HamcrestMatchers extends Specification { def "comparing two decimal numbers"() { def myPi = 3.14 expect: myPi closeTo(Math.PI, 0.01) } }

Page 53: Tests en Java con Groovy y Spock

Control de la EjecuciónControl de la Ejecución

@Ignoredef "esta no se va a ejecutar"() { }

@Ignore(reason = "porque no funciona ni p'atrás")def "esta tampoco se va a ejecutar"() { }

@IgnoreRestdef "si lo pongo esta va a ser la única en ejecutarse"() { }

@IgnoreIf({ os.windows })def "esta solo se ejecutaría en Windows"() { }

@Stepwiseclass RunInOrderSpec extends Specification { def "Este será siempre el primero"() { ... } def "Este se ejecutará el segundo"() { ... }}

@Timeout(5)def "Falla si tarda más de 5 segundos"() { }

Ejecuciónselectiva

Timeout

Orden deejecución

Page 54: Tests en Java con Groovy y Spock

Tests basados enTests basados enDatosDatos

Page 55: Tests en Java con Groovy y Spock

@Unroll def "distance on #descrip"() { expect: LevenshteinCalculator.getLevenshteinDistance(s1, s2) == res (res == 0? s1 == s2 : s1 != s2) where: s1 | s2 | descrip || res "pepito" | "pepito" | "same values" || 0 "pepito" | "pePito" | "only case difference" || 1 "pepito" | "qerida" | "many char differences" || 4 "pepito" | "p" | "shorter value" || 5 "p" | "otro" | "larger value" || 4 "12345" | "6" | "all different" || 5 "" | "1234" | "empty and non empty" || 4 "" | "" | "both empty" || 0 "12 34" | "12 34" | "differences in spaces" || 1 "one vision"| "one visn" | "two chars in the middle" || 2 }

Tablas de datosTablas de datos

Page 56: Tests en Java con Groovy y Spock

@Unroll def "distance on #descrip"() { expect: LevenshteinCalculator.getLevenshteinDistance(s1, s2) == res (res == 0? s1 == s2 : s1 != s2) where: s1 | s2 | descrip || res "pepito" | "pepito" | "same values" || 0 "pepito" | "pePito" | "only case difference" || 1 "pepito" | "qerida" | "many char differences" || 4 "pepito" | "p" | "shorter value" || 5 "p" | "otro" | "larger value" || 4 "12345" | "6" | "all different" || 5 "" | "1234" | "empty and non empty" || 4 "" | "" | "both empty" || 0 "12 34" | "12 34" | "differences in spaces" || 1 "one vision"| "one visn" | "two chars in the middle" || 2 }

Tablas de datosTablas de datos

Page 57: Tests en Java con Groovy y Spock

@Unroll def "distance on #descrip"() { expect: LevenshteinCalculator.getLevenshteinDistance(s1, s2) == res (res == 0? s1 == s2 : s1 != s2) where: s1 | s2 | descrip || res "pepito" | "pepito" | "same values" || 0 "pepito" | "pePito" | "only case difference" || 1 "pepito" | "qerida" | "many char differences" || 4 "pepito" | "p" | "shorter value" || 5 "p" | "otro" | "larger value" || 4 "12345" | "6" | "all different" || 5 "" | "1234" | "empty and non empty" || 4 "" | "" | "both empty" || 0 "12 34" | "12 34" | "differences in spaces" || 1 "one vision"| "one visn" | "two chars in the middle" || 2 }

Tablas de datosTablas de datos

Page 58: Tests en Java con Groovy y Spock

@Unroll def "distance on #descrip"() { expect: LevenshteinCalculator.getLevenshteinDistance(s1, s2) == res (res == 0? s1 == s2 : s1 != s2) where: s1 | s2 | descrip || res "pepito" | "pepito" | "same values" || 0 "pepito" | "pePito" | "only case difference" || 1 "pepito" | "qerida" | "many char differences" || 4 "pepito" | "p" | "shorter value" || 5 "p" | "otro" | "larger value" || 4 "12345" | "6" | "all different" || 5 "" | "1234" | "empty and non empty" || 4 "" | "" | "both empty" || 0 "12 34" | "12 34" | "differences in spaces" || 1 "one vision"| "one visn" | "two chars in the middle" || 2 }

Tablas de datosTablas de datos

Tests diferenciados

Page 59: Tests en Java con Groovy y Spock

Pipes de datosPipes de datos

@Unroll def "distance on #descrip"() { expect: LevenshteinCalculator.getLevenshteinDistance(s1, s2) == res (res == 0? s1 == s2 : s1 != s2) where: s1 << ["pepito", "pepito", "pepito", "pepito", "p"] s2 << ["pepito", "pePito", "qerida", "p", "otro"] descrip << ["same values", "only case difference", "many char differences", "shorter value", "larger value"] res << [0, 1, 4, 5, 4] }

Page 60: Tests en Java con Groovy y Spock

Pipes de datosPipes de datos

@Unroll def "distance on #descrip"() { expect: LevenshteinCalculator.getLevenshteinDistance(s1, s2) == res (res == 0? s1 == s2 : s1 != s2) where: s1 << ["pepito", "pepito", "pepito", "pepito", "p"] s2 << ["pepito", "pePito", "qerida", "p", "otro"] descrip << ["same values", "only case difference", "many char differences", "shorter value", "larger value"] res << [0, 1, 4, 5, 4] }

Page 61: Tests en Java con Groovy y Spock

Pipes de datosPipes de datos

@Unroll def "distance on #descrip"() { expect: LevenshteinCalculator.getLevenshteinDistance(s1, s2) == res (res == 0? s1 == s2 : s1 != s2) where: [s1, s2, descrip, resStr] << new File("testdata.csv").readLines() .collect {line -> line.tokenize(",")} res = Integer.parseInt(resStr) }

Page 62: Tests en Java con Groovy y Spock

Pipes de datosPipes de datos

@Unroll def "distance on #descrip"() { expect: LevenshteinCalculator.getLevenshteinDistance(s1, s2) == res (res == 0? s1 == s2 : s1 != s2) where: [s1, s2, descrip, resStr] << new File("testdata.csv").readLines() .collect {line -> line.tokenize(",")} res = Integer.parseInt(resStr) }

Page 63: Tests en Java con Groovy y Spock

Pipes de datosPipes de datos

@Unroll def "distance on #descrip"() { expect: LevenshteinCalculator.getLevenshteinDistance(s1, s2) == res (res == 0? s1 == s2 : s1 != s2) where: [s1, s2, descrip, resStr] << new File("testdata.csv").readLines() .collect {line -> line.tokenize(",")} res = Integer.parseInt(resStr) }

Asignaciones de variables

Page 64: Tests en Java con Groovy y Spock

““Test doubles”Test doubles”(mock objects)(mock objects)

Page 65: Tests en Java con Groovy y Spock

¿Por qué “test doubles”?¿Por qué “test doubles”?

Problema: test de clase A que usa otra clase B que no queremos probar:

Porque utiliza recursos externos (BD, APIs externas...)

Para independizar las pruebas

“Test doubles” reemplazan la clase B por objetos “de pega”

Stub: devuelve respuestas prefijadas en el test

Mock: cascarón vacío con respuestas por defecto

Spy: pone una capa sobre un objeto real para espiar las llamadas que se le hacen

Page 66: Tests en Java con Groovy y Spock

StubsStubs

def "check valid comics"() { given: def apiStub = Stub(MarvelApi) { findComicsByCharacter(_) >> [ new MarvelComic(id: 1, date: new Date(), creators: [new ComicCreator(id: 101)] ), (............) new MarvelComic(id: 6, date: null, creators: [new ComicCreator(id: 103)] ) ] } MarvelQuestionnaireFactory f = new MarvelQuestionFactory(apiStub) expect: f.loadValidQuestionnarieComics(1)*.id == [1, 5] }

Page 67: Tests en Java con Groovy y Spock

StubsStubs

def "check valid comics"() { given: def apiStub = Stub(MarvelApi) { findComicsByCharacter(_) >> [ new MarvelComic(id: 1, date: new Date(), creators: [new ComicCreator(id: 101)] ), (............) new MarvelComic(id: 6, date: null, creators: [new ComicCreator(id: 103)] ) ] } MarvelQuestionnaireFactory f = new MarvelQuestionFactory(apiStub) expect: f.loadValidQuestionnarieComics(1)*.id == [1, 5] }

Stub de una claseañadir dependencias a

cglib-nodep y objenesis

Page 68: Tests en Java con Groovy y Spock

StubsStubs

def "check valid comics"() { given: def apiStub = Stub(MarvelApi) { findComicsByCharacter(_) >> [ new MarvelComic(id: 1, date: new Date(), creators: [new ComicCreator(id: 101)] ), (............) new MarvelComic(id: 6, date: null, creators: [new ComicCreator(id: 103)] ) ] } MarvelQuestionnaireFactory f = new MarvelQuestionFactory(apiStub) expect: f.loadValidQuestionnarieComics(1)*.id == [1, 5] }

Named parameter constructor

Page 69: Tests en Java con Groovy y Spock

Stubs: constraintsStubs: constraints

Método: admite expresiones regulares

api./findComicsBy.*/(...)

Propiedad (getter)

api.apiKey

Argumentos

stub.metodo("hello")stub.metodo(!"hello")stub.metodo()stub.metodo(_)stub.metodo(*_)stub.metodo(_ as String)stub.metodo({ l -> l.size() > 3 })

Page 70: Tests en Java con Groovy y Spock

Stubs: comportamientoStubs: comportamiento

Siempre devolver mismo valor (>>)

stub.metodo(args) >> result1

Devolver valores secuencialmente (>>>)

stub.metodo(args) >>> [res1, res2, res3]

Ejecución de código (cambio estado, calcular retorno)

stub.metodo(...) >> { args -> ..... }

stub.metodo(...) >> { arg -> ..... }

Encadenar llamadas de distinto tipo

stub.metodo(args) >>> [r1, r2] >> { (code) } >> r4

Llamada no declarada: valor por defecto / objeto vacío (no null)

Page 71: Tests en Java con Groovy y Spock

Tests basados enTests basados enInteraccionesInteracciones

Page 72: Tests en Java con Groovy y Spock

ExternalEvent Log

System

QuestionnaireDAO DB

Event LogAPI

QuestionnaireService

No hay resultado que probar

Añadir unAñadir uncuestionariocuestionario

Tests de Interacciones: por quéTests de Interacciones: por qué

Page 73: Tests en Java con Groovy y Spock

ExternalEvent Log

System

QuestionnaireDAO DB

Event LogAPI

QuestionnaireService

No hay resultado que probar

Añadir unAñadir uncuestionariocuestionario

Tests de Interacciones: por quéTests de Interacciones: por qué

¡¡¡N

O L

O PROBAM

OS

!!!

Page 74: Tests en Java con Groovy y Spock

Interacción con MocksInteracción con Mocks

def "add a questionnaire"() { given: "a questionnaire with two questions" def q = new Questionnaire() q.addQuestion(new Question()) q.addQuestion(new Question())

and: "a service with mocked collaborators" def dao = Mock(QuestionnaireDao) def eventLog = Mock(EventLogApi) def service = new QuestionnaireService(dao, eventLog) when: "the questionnaire is created" service.addQuestionnaire(q) then: "the questionnaire + questions are created, the event logged" 1 * dao.addQuestionnaireBean(_) 2 * dao.addQuestionBean(_) 1 * eventLog.registerEvent { ev -> ev.type == EventType.ADD_QUESTIONNAIRE } }

Page 75: Tests en Java con Groovy y Spock

Interacción con MocksInteracción con Mocks

def "add a questionnaire"() { given: "a questionnaire with two questions" def q = new Questionnaire() q.addQuestion(new Question()) q.addQuestion(new Question())

and: "a service with mocked collaborators" def dao = Mock(QuestionnaireDao) def eventLog = Mock(EventLogApi) def service = new QuestionnaireService(dao, eventLog) when: "the questionnaire is created" service.addQuestionnaire(q) then: "the questionnaire + questions are created, the event logged" 1 * dao.addQuestionnaireBean(_) 2 * dao.addQuestionBean(_) 1 * eventLog.registerEvent { ev -> ev.type == EventType.ADD_QUESTIONNAIRE } }

Page 76: Tests en Java con Groovy y Spock

Interacción con MocksInteracción con Mocks

def "add a questionnaire"() { given: "a questionnaire with two questions" def q = new Questionnaire() q.addQuestion(new Question()) q.addQuestion(new Question())

and: "a service with mocked collaborators" def dao = Mock(QuestionnaireDao) def eventLog = Mock(EventLogApi) def service = new QuestionnaireService(dao, eventLog) when: "the questionnaire is created" service.addQuestionnaire(q) then: "the questionnaire + questions are created, the event logged" 1 * dao.addQuestionnaireBean(_) 2 * dao.addQuestionBean(_) 1 * eventLog.registerEvent { ev -> ev.type == EventType.ADD_QUESTIONNAIRE } }

Interacción = Cardinalidad * Constraint

Page 77: Tests en Java con Groovy y Spock

Mocks en SpockMocks en Spock

Cardinalidad

Constraints son iguales que en Stubs

Mocking por defecto lenient (“indulgente”)

Estricto - añadir al final regla: 0 * _

Orden de llamadas no se considera

Para hacerlo, poner cada comprobación en un bloque “then” diferenciado

1 * subscriber.receive("hello")0 * subscriber.receive("hello")(1..3) * subscriber.receive("hello")(1.._) * subscriber.receive("hello")(_..3) * subscriber.receive("hello")_ * subscriber.receive("hello")

Page 78: Tests en Java con Groovy y Spock

Shaken, not stirredShaken, not stirred

Interacciones se pueden mezclar con condiciones de comprobación de datos

Mocks pueden tener métodos stubbeados

Valores por defecto distintos a Stub: 0 / false / null

Spies: wrapper sobre implementación de clase real

Se pueden chequear interacciones

Se pueden stubbear métodos

Page 79: Tests en Java con Groovy y Spock

Shaken, not stirredShaken, not stirred

Interacciones se pueden mezclar con condiciones de comprobación de datos

Mocks pueden tener métodos stubbeados

Valores por defecto distintos a Stub: 0 / false / null

Spies: wrapper sobre implementación de clase real

Se pueden chequear interacciones

Se pueden stubbear métodos

Page 80: Tests en Java con Groovy y Spock

Recapitulemos...Recapitulemos...

Page 81: Tests en Java con Groovy y Spock

¿¿¿Tests a prueba de Vagos???¿¿¿Tests a prueba de Vagos???

Subir nivel de abstracción

No “programar tests” → declarar casos de prueba

Sencillez + potencia

Expresividad → test es a la vez documentación

Fácil de ejecutar en sistemas de integración continua y en IDEs

Información que facilite la detección de errores

Page 82: Tests en Java con Groovy y Spock

¿¿¿Tests a prueba de Vagos???¿¿¿Tests a prueba de Vagos???

Subir nivel de abstracción

No “programar tests” → declarar casos de prueba

Sencillez + potencia

Expresividad → test es a la vez documentación

Fácil de ejecutar en sistemas de integración continua y en IDEs

Información que facilite la detección de errores

Page 83: Tests en Java con Groovy y Spock

¿¿¿Tests a prueba de Vagos???¿¿¿Tests a prueba de Vagos???

Subir nivel de abstracción

No “programar tests” → declarar casos de prueba

Sencillez + potencia

Expresividad → test es a la vez documentación

Fácil de ejecutar en sistemas de integración continua y en IDEs

Información que facilite la detección de errores

Page 84: Tests en Java con Groovy y Spock

¿¿¿Tests a prueba de Vagos???¿¿¿Tests a prueba de Vagos???

Subir nivel de abstracción

No “programar tests” → declarar casos de prueba

Sencillez + potencia

Expresividad → test es a la vez documentación

Fácil de ejecutar en sistemas de integración continua y en IDEs

Información que facilite la detección de errores

Page 85: Tests en Java con Groovy y Spock

¿¿¿Tests a prueba de Vagos???¿¿¿Tests a prueba de Vagos???

Subir nivel de abstracción

No “programar tests” → declarar casos de prueba

Sencillez + potencia

Expresividad → test es a la vez documentación

Fácil de ejecutar en sistemas de integración continua y en IDEs

Información que facilite la detección de errores

¡¡¡YESSSSSSSSSSSSS!!!¡¡¡YESSSSSSSSSSSSS!!!

Page 86: Tests en Java con Groovy y Spock

¿Ibas a alguna parte?...

Page 87: Tests en Java con Groovy y Spock

¡¡¡¿¿¿SOMOS HOMBRES¡¡¡¿¿¿SOMOS HOMBRESO NENAZAS???!!!O NENAZAS???!!!

¿Ibas a alguna parte?...

Page 88: Tests en Java con Groovy y Spock

Tests de integraciónTests de integración

Page 89: Tests en Java con Groovy y Spock

Base de datos (objeto Sql)Base de datos (objeto Sql) @Shared @AutoCleanup("shutdown") DataSource ds = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).build(); @Shared Sql sql = Sql.newInstance(ds)

@Shared SqlSession session

@Shared @Subject QuestionnarieDao dao def setupSpec() { // DDL sql.execute(''' create table questionnaries ( id bigint not null identity, name varchar(200) not null ); ''') // MyBatis config / DAO creation def transactionFactory = new JdbcTransactionFactory(); def environment = new Environment("development", transactionFactory, ds); def configuration = new Configuration(environment); configuration.addMapper(QuestionnarieDao.class); def builder = new SqlSessionFactoryBuilder(); def factory = builder.build(configuration); session = factory.openSession() dao = session.getMapper(QuestionnarieDao.class) }

Page 90: Tests en Java con Groovy y Spock

Base de datos (objeto Sql)Base de datos (objeto Sql) @Shared @AutoCleanup("shutdown") DataSource ds = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).build(); @Shared Sql sql = Sql.newInstance(ds)

@Shared SqlSession session

@Shared @Subject QuestionnarieDao dao def setupSpec() { // DDL sql.execute(''' create table questionnaries ( id bigint not null identity, name varchar(200) not null ); ''') // MyBatis config / DAO creation def transactionFactory = new JdbcTransactionFactory(); def environment = new Environment("development", transactionFactory, ds); def configuration = new Configuration(environment); configuration.addMapper(QuestionnarieDao.class); def builder = new SqlSessionFactoryBuilder(); def factory = builder.build(configuration); session = factory.openSession() dao = session.getMapper(QuestionnarieDao.class) }

Page 91: Tests en Java con Groovy y Spock

Base de datos (objeto Sql)Base de datos (objeto Sql) @Shared @AutoCleanup("shutdown") DataSource ds = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).build(); @Shared Sql sql = Sql.newInstance(ds)

@Shared SqlSession session

@Shared @Subject QuestionnarieDao dao def setupSpec() { // DDL sql.execute(''' create table questionnaries ( id bigint not null identity, name varchar(200) not null ); ''') // MyBatis config / DAO creation def transactionFactory = new JdbcTransactionFactory(); def environment = new Environment("development", transactionFactory, ds); def configuration = new Configuration(environment); configuration.addMapper(QuestionnarieDao.class); def builder = new SqlSessionFactoryBuilder(); def factory = builder.build(configuration); session = factory.openSession() dao = session.getMapper(QuestionnarieDao.class) }

Page 92: Tests en Java con Groovy y Spock

Base de datos (objeto Sql)Base de datos (objeto Sql) def "find questionnaries" () { final NAME = "Cuestionario de prueba" given: sql.execute("insert into questionnaries(name) values (${NAME})") sql.commit() when: def qlist = dao.findActiveQuestionnaries()

then: qlist.size() == 1 qlist[0].name == NAME } def "insert questionnarie" () { final NAME = "Cuestionario nuevo" when: dao.insertQuestionnarie(new Questionnarie([name: NAME])) session.commit() then: sql.firstRow("select * from questionnaries where name = ${NAME}").id != null and: sql.rows("select * from questionnaries").size() == old(sql.rows("select * from questionnaries").size()) + 1 }

Page 93: Tests en Java con Groovy y Spock

Base de datos: DB UnitBase de datos: DB Unit

@Shared @AutoCleanup("shutdown") DataSource ds = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).build() (...)

@DbUnit def dbState = { Questionnaries(id: 1, name: 'Cuestionario de prueba') Questionnaries(id: 2, name: 'Otro cuestionario') Questionnaries(id: 3, name: 'Y otro más') }

(...) def "find questionnaries" () { when: def qlist = dao.findActiveQuestionnaries()

then: qlist.size() == 3 qlist[0].name == "Cuestionario de prueba" }

Page 94: Tests en Java con Groovy y Spock

Base de datos: DB UnitBase de datos: DB Unit

@Shared @AutoCleanup("shutdown") DataSource ds = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).build() (...)

@DbUnit def dbState = { Questionnaries(id: 1, name: 'Cuestionario de prueba') Questionnaries(id: 2, name: 'Otro cuestionario') Questionnaries(id: 3, name: 'Y otro más') }

(...) def "find questionnaries" () { when: def qlist = dao.findActiveQuestionnaries()

then: qlist.size() == 3 qlist[0].name == "Cuestionario de prueba" }

spock-dbunit

Page 95: Tests en Java con Groovy y Spock

SpringSpring

@ContextConfiguration(locations = "classpath:spring/application-config.xml")class CourseRestControllerSpec extends Specification { @Autowired @Subject CourseRestController controller def "get courses"() { when: ListPage<Course> courses = controller.getCourses(new PaginationDesc(from: 1, max: 10)) then: courses.listSize == 7 courses.elements[2].title == "Intensivo de rueda cubana" }}

Page 96: Tests en Java con Groovy y Spock

SpringSpring

@ContextConfiguration(locations = "classpath:spring/application-config.xml")class CourseRestControllerSpec extends Specification { @Autowired @Subject CourseRestController controller def "get courses"() { when: ListPage<Course> courses = controller.getCourses(new PaginationDesc(from: 1, max: 10)) then: courses.listSize == 7 courses.elements[2].title == "Intensivo de rueda cubana" }}

spock-spring

Page 97: Tests en Java con Groovy y Spock

TestsTestsFuncionalesFuncionales

(web)(web)

Page 98: Tests en Java con Groovy y Spock

Tests web funcionalesTests web funcionales

class QuestionnariesPageSpec extends GebSpec { def "questionnaries page check"() { final EXPECTED_ELEMENT = "Cuestionario chulo" given: go "/es/questionnaries" expect: $("p.recordcount > .valor").text() == "7" and: def link = $("ol.pag-registros > li .media-heading a")[5] link.text() == EXPECTED_ELEMENT when: link.click() then: title == EXPECTED_ELEMENT }}

Page 99: Tests en Java con Groovy y Spock

Tests web funcionalesTests web funcionales

class QuestionnariesPageSpec extends GebSpec { def "questionnaries page check"() { final EXPECTED_ELEMENT = "Cuestionario chulo" given: go "/es/questionnaries" expect: $("p.recordcount > .valor").text() == "7" and: def link = $("ol.pag-registros > li .media-heading a")[5] link.text() == EXPECTED_ELEMENT when: link.click() then: title == EXPECTED_ELEMENT }}

Page 100: Tests en Java con Groovy y Spock

GebGeb

Very Groovy Browser Automation

Basado en Selenium

http://www.gebish.org/

Permite hacer capturas (reporting)<dependency> <groupId>org.gebish</groupId> <artifactId>geb-spock</artifactId> <version>0.9.2</version> <scope>test</scope></dependency><dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-htmlunit-driver</artifactId> <version>2.26.0</version> <scope>test</scope></dependency><dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-firefox-driver</artifactId> <version>2.26.0</version> <scope>test</scope></dependency>

Dependencias

Configuracioń:/GebConfig.groovy (DSL)

import org.openqa.selenium.htmlunit.HtmlUnitDriver;

driver = { new HtmlUnitDriver() }baseUrl = "http://xxxxxxxxxxxxxxxxx"

Instalar Drivers (PhantomJS, Firefox...)

Page 101: Tests en Java con Groovy y Spock

Geb con Page ObjectsGeb con Page Objects

class PaginationModule extends Module { def root static content = { paginationbar { root.find(".paginationbar") } total { paginationbar.find (".recordcount .valor").text() as int } pageElements { root.find("ol.pag-registros > li") } }} class QuestionnarieHeader extends Module { def root static content = { link { root.find(".media-heading a") } description { link.text() } }}

class QuestionnariesListPage extends Page { static url = "/es/questionnaries" static at = { title == "Registros" } static content = { pagination { module PaginationModule, root: $(".sumario_registros .pagination-container") } questionnaries { pagination.pageElements.collect { module QuestionnarieHeader, root: it } } }}

Page 102: Tests en Java con Groovy y Spock

Geb con Page ObjectsGeb con Page Objects

class PaginationModule extends Module { def root static content = { paginationbar { root.find(".paginationbar") } total { paginationbar.find (".recordcount .valor").text() as int } pageElements { root.find("ol.pag-registros > li") } }} class QuestionnarieHeader extends Module { def root static content = { link { root.find(".media-heading a") } description { link.text() } }}

class QuestionnariesListPage extends Page { static url = "/es/questionnaries" static at = { title == "Registros" } static content = { pagination { module PaginationModule, root: $(".sumario_registros .pagination-container") } questionnaries { pagination.pageElements.collect { module QuestionnarieHeader, root: it } } }}

Page object- url: para ir a la página- at: para comprobar si estamos en ella- content: acceso rápido a elementos

Page 103: Tests en Java con Groovy y Spock

Geb con Page ObjectsGeb con Page Objects

class PaginationModule extends Module { def root static content = { paginationbar { root.find(".paginationbar") } total { paginationbar.find (".recordcount .valor").text() as int } pageElements { root.find("ol.pag-registros > li") } }} class QuestionnarieHeader extends Module { def root static content = { link { root.find(".media-heading a") } description { link.text() } }}

class QuestionnariesListPage extends Page { static url = "/es/questionnaries" static at = { title == "Registros" } static content = { pagination { module PaginationModule, root: $(".sumario_registros .pagination-container") } questionnaries { pagination.pageElements.collect { module QuestionnarieHeader, root: it } } }}

Module objectElemento reutilizable por varias páginas

Page 104: Tests en Java con Groovy y Spock

Geb con Page ObjectsGeb con Page Objects

class PaginationModule extends Module { def root static content = { paginationbar { root.find(".paginationbar") } total { paginationbar.find (".recordcount .valor").text() as int } pageElements { root.find("ol.pag-registros > li") } }} class QuestionnarieHeader extends Module { def root static content = { link { root.find(".media-heading a") } description { link.text() } }}

class QuestionnariesListPage extends Page { static url = "/es/questionnaries" static at = { title == "Registros" } static content = { pagination { module PaginationModule, root: $(".sumario_registros .pagination-container") } questionnaries { pagination.pageElements.collect { module QuestionnarieHeader, root: it } } }}

Forma de usar los módulos dentro de un page object

Page 105: Tests en Java con Groovy y Spock

Geb con Page ObjectsGeb con Page Objects

class QuestionnariesPageSpec extends GebSpec { def "questionnaries page check"() { final EXPECTED_ELEMENT = "Cuestionario chulo"

given: to QuestionnariesListPage expect: at QuestionnariesListPage and: pagination.total == 7 and: def quest = questionnaries[5] quest.description == EXPECTED_ELEMENT when: quest.link.click() then: waitFor { at QuestionnariePage } questionnarieTitle == EXPECTED_ELEMENT }}

Page 106: Tests en Java con Groovy y Spock

Tests deTests deAceptaciónAceptación

Page 107: Tests en Java con Groovy y Spock

Pruebas de aceptaciónPruebas de aceptación@Title("Listado de cuestionarios")@Narrative("""" Como creador de juegos de cuestionarios quiero poder consultar la lista de cuestionarios ya existentes para poder crear un nuevo cuestionario basado en otro anterior""")class QuestionnariesPageSpec extends GebSpec { def "scenario: comprobación listado"() { final EXPECTED_ELEMENT = "Cuestionario chulo"

given: "Estamos en la lista de cuestionarios" to QuestionnariesListPage expect: "Que la página sea la correcta" at QuestionnariesListPage and: "El número de elementos sea el correcto" pagination.total == 222 and: "Se comprueba que uno de los elementos sea el correcto" def quest = questionnaries[5] quest.description == EXPECTED_ELEMENT when: "Se clica en él" quest.link.click() then: "Se comprueba que se va a su ficha y que el título sea el correcto" waitFor { at QuestionnariePage } questionnarieTitle == EXPECTED_ELEMENT }}

Historia de usuario

Page 108: Tests en Java con Groovy y Spock

Pruebas de aceptaciónPruebas de aceptación@Title("Listado de cuestionarios")@Narrative("""" Como creador de juegos de cuestionarios quiero poder consultar la lista de cuestionarios ya existentes para poder crear un nuevo cuestionario basado en otro anterior""")class QuestionnariesPageSpec extends GebSpec { def "scenario: comprobación listado"() { final EXPECTED_ELEMENT = "Cuestionario chulo"

given: "Estamos en la lista de cuestionarios" to QuestionnariesListPage expect: "Que la página sea la correcta" at QuestionnariesListPage and: "El número de elementos sea el correcto" pagination.total == 222 and: "Se comprueba que uno de los elementos sea el correcto" def quest = questionnaries[5] quest.description == EXPECTED_ELEMENT when: "Se clica en él" quest.link.click() then: "Se comprueba que se va a su ficha y que el título sea el correcto" waitFor { at QuestionnariePage } questionnarieTitle == EXPECTED_ELEMENT }}

Criterios de aceptación

Page 109: Tests en Java con Groovy y Spock

Pruebas de aceptaciónPruebas de aceptación@Title("Listado de cuestionarios")@Narrative("""" Como creador de juegos de cuestionarios quiero poder consultar la lista de cuestionarios ya existentes para poder crear un nuevo cuestionario basado en otro anterior""")class QuestionnariesPageSpec extends GebSpec { def "scenario: comprobación listado"() { final EXPECTED_ELEMENT = "Cuestionario chulo"

given: "Estamos en la lista de cuestionarios" to QuestionnariesListPage expect: "Que la página sea la correcta" at QuestionnariesListPage and: "El número de elementos sea el correcto" pagination.total == 222 and: "Se comprueba que uno de los elementos sea el correcto" def quest = questionnaries[5] quest.description == EXPECTED_ELEMENT when: "Se clica en él" quest.link.click() then: "Se comprueba que se va a su ficha y que el título sea el correcto" waitFor { at QuestionnariePage } questionnarieTitle == EXPECTED_ELEMENT }}

Cooperación cliente, UX, front, back...

Page 110: Tests en Java con Groovy y Spock

Pruebas de aceptaciónPruebas de aceptación@Title("Listado de cuestionarios")@Narrative("""" Como creador de juegos de cuestionarios quiero poder consultar la lista de cuestionarios ya existentes para poder crear un nuevo cuestionario basado en otro anterior""")class QuestionnariesPageSpec extends GebSpec { def "scenario: comprobación listado"() { final EXPECTED_ELEMENT = "Cuestionario chulo"

given: "Estamos en la lista de cuestionarios" to QuestionnariesListPage expect: "Que la página sea la correcta" at QuestionnariesListPage and: "El número de elementos sea el correcto" pagination.total == 222 and: "Se comprueba que uno de los elementos sea el correcto" def quest = questionnaries[5] quest.description == EXPECTED_ELEMENT when: "Se clica en él" quest.link.click() then: "Se comprueba que se va a su ficha y que el título sea el correcto" waitFor { at QuestionnariePage } questionnarieTitle == EXPECTED_ELEMENT }}

BDDBehaviour Driven

Development

Cooperación cliente, UX, front, back...

Page 111: Tests en Java con Groovy y Spock

Más informaciónMás información

Página principal Spock: http://www.spockframework.org

Documentación: http://docs.spockframework.org/

Documentación antigua:http://code.google.com/p/spock/w/list

Spock Web Consolehttp://meet.spockframework.org/

Proyecto de ejemplohttp://files.spockframework.org/spock-example-0.5-groovy-1.7.zip

Lenguaje Groovyhttp://beta.groovy-lang.org/docs/groovy-2.3.1/html/documentation/#_lists

Modificaciones Groovy a librería estándar JDKhttp://groovy.codehaus.org/groovy-jdk/

Page 112: Tests en Java con Groovy y Spock

Más informaciónMás información

Gebhttp://www.gebish.org/

spock-spring http://code.google.com/p/spock/wiki/SpringExtension

spock-dbunithttps://github.com/janbols/spock-dbunit

Page 113: Tests en Java con Groovy y Spock

Gracias por la atención... Gracias por la atención...

Andrés ViedmaAndrés Viedma@andres_viedma@andres_viedma