Dmitry Voloshko
Smarter Testing
With Spock
What we’ll talk about
Spock?!
State Based Testing
Data Driven Testing
Interaction Based Testing
Spock Extensions
More Cool Stuff
Spock?!
Spock is...
A developer testing framework...
for Groovy and Java applications...
based on Groovy...
fully compatible with JUnit...
but going beyond!
Spock Can...
Reduce the lines of test code
Make tests more readable
Turn tests into specifications
Be extended in powerful ways
Bring back the fun to testing!
Getting Started
Homepage
http://spockframework.org
Source Code
https://github.com/spockframework/spock
Spock Web Console
http://meet.spockframework.org
Spock Example Project
http://downloads.spockframework.org
https://github.com/spockframework/spock/tree/groovy-1.8/spock-
example
Who’s Using Spock?
Gradle GPars
Grails Geb
Grails Plugin Collective
Apache Tapestry
Griffon Spock
Spring?
Who’s Using Spock?
Betfair be2
bemoko BSkyB CTI Digital
Donewtech Solutions
eHarmony
Energized Work Gennemtænkt IT
IntelliGrape Software Netflix
Smarter Ecommerce STERMEDIA Sp. z o.o.
Software Projects Spot Diving
SystemsForge TouK
State Based Testing
State Based Testing
Classical Unit Testing
Arrange
Act
Assert
Given-When-Then
The Code For Testing
Account.java
public class Account {
private BigDecimal balance = new BigDecimal(0);
public Account(BigDecimal initial) {
balance = initial;
}
public BigDecimal getBalance() {
return balance;
}
public void withdraw(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new NegativeAmountWithdrawnException(amount);
}
balance = balance.subtract(amount);
}
}
The Code For Testing
NegativeAmountWithdrawnException.java
public class NegativeAmountWithdrawnException extends RuntimeException {
private final BigDecimal amount;
public NegativeAmountWithdrawnException(BigDecimal amount) {
super("cannot withdraw" + amount);
this.amount = amount;
}
public BigDecimal getAmount() {
return amount;
}
}
Show me the code
public class AccountTest {
@Test
public void withdrawSomeAmount() {
// given
Account account = new Account(BigDecimal.valueOf(5));
// when
account.withdraw(BigDecimal.valueOf(2));
// then
assertEquals(BigDecimal.valueOf(3), account.getBalance());
}
}
Show me the code
class AccountSpec extends Specification {
def "withdraw some amount"() {
given:
Account account = new Account(BigDecimal.valueOf(5));
when:
account.withdraw(BigDecimal.valueOf(2));
then:
account.getBalance() == BigDecimal.valueOf(3);
}
}
Show me the code
class AccountSpec2 extends Specification {
def "withdraw some amount"() {
given:
def account = new Account(5.0)
when:
account.withdraw(2.0)
then:
account.balance == 3.0
}
}
Show me the code
class AccountSpec3 extends Specification {
def "withdraw some amount"() {
given: "an account with a balance of five euros"
def account = new Account(5.0)
when: "two euros are withdrawn"
account.withdraw(2.0)
then: "three euros remain in the account"
account.balance == 3.0
}
}
Show me the code
class AccountSpec4 extends Specification {
def "can't withdraw a negative amount"() {
given:
def account = new Account(5.0)
when:
account.withdraw(-1.0)
then:
NegativeAmountWithdrawnException e = thrown()
e.amount == -1.0
}
def "withdrawing some amount decreases the balance by exactly that amount"() {
def account = new Account(5.0)
when:
account.withdraw(2.0)
then:
account.balance == old(account.balance) - 2.0
}
}
Show me the code
class SharedField extends Specification {
@Shared File file
def "a file based test"() {
when:
file = new File("foo.txt")
file.createNewFile()
then:
file.exists()
}
def "by now the object is not null"() {
expect:
file.exists()
cleanup:
file.delete()
}
}
Show me the code
class SetupSpecSpec extends Specification {
File file
def setup() {
file = new File("foo2.txt")
}
def "a file based test"() {
when:
file.createNewFile()
then:
file.exists()
}
def "by now the object is not null"() {
expect: file.exists()
cleanup: file.delete()
}
}
Show me the code
class Conditions extends Specification {
def "when-then style"() {
when:
def x = Math.max(5, 9)
then:
x == 9
}
def "expect style"() {
expect:
Math.max(5, 9) == 9
}
def "more complex conditions"() {
expect:
germanCarBrands.any { it.size() >= 3 }
}
private getGermanCarBrands() { ["audi", "bmw", "porsche"] }
}
Recap: State Based Testing
Blocks
setup: cleanup: expect: given: when: then:
where: and:
Fixture Methods
setup() cleanup() setupSpec() cleanupSpec()
Instance and @Shared fields
old() and thrown()
Data Driven Testing
Data Driven Testing
Test the same behavior...
with varying data!
Show me the code
class AccountSpecDataDriven extends Specification {
@Unroll
def "withdraw some amount"() {
given:
def account = new Account(balance)
when:
account.withdraw(withdrawn)
then:
account.balance == remaining
where:
balance | withdrawn | |remaining
5.0 | 2.0 | | 3.0
4.0 | 0.0 | | 4.0
4.0 | 4.0 | | 0.0
}
}
Show me the code
class AccountSpecDataDriven extends Specification {
@Unroll
def "withdrawing #withdrawn from account with balance #balance"() {
given:
def account = new Account(balance)
when:
account.withdraw(withdrawn)
then:
account.balance == old(account.balance) – withdrawn
where:
balance | withdrawn
5.0 | 2.0
4.0 | 0.0
4.0 | 4.0
}
}
Show me the code
class AccountSpecDatabaseDriven extends Specification {
@Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")
def setupSpec() {
sql.execute("create table accountdata (id int primary key, balance decimal, withdrawn decimal, remaining decimal)")
sql.execute("insert into accountdata values (1, 5.0, 2.0, 3.0), (2, 4.0, 0.0, 4.0), (3, 4.0, 4.0, 0.0)")
}
@Unroll def "withdrawing #withdrawn from account with balance #balance leaves #remaining"() {
given:
def account = new Account(balance)
when:
account.withdraw(withdrawn)
then:
account.balance == remaining
where:
[balance, withdrawn, remaining] << sql.rows("select balance, withdrawn, remaining from accountdata")
}
}
Recap: Data Driven Testing
where: block
Data tables
External data sources
@Unroll
Interaction Based Testing
Interaction Based Testing
Design and test how your objects
communicate
Mocking frameworks to the rescue
Spock comes with its own mocking
framework
The Code For Testing
PublisherSubscriber.groovy
interface Subscriber {
void receive(String message)
}
class Publisher {
List<Subscriber> subscribers = []
void publish(String message) {
for (subscriber in subscribers) {
try {
subscriber.receive(message)
} catch (ignored) {}
}
}
}
The Code For Testing
PublisherSubscriber.groovy
interface Subscriber2 {
void receive(String message)
boolean isActive()
}
class Publisher2 {
List<Subscriber2> subscribers = []
void publish(String message) {
for (subscriber in subscribers) {
try {
if (subscriber.active) {
subscriber.receive(message)
}
} catch (ignored) {}
}
}
}
Show me the code
class PublisherSubscriberSpec extends Specification {
def pub = new Publisher()
def sub1 = Mock(Subscriber)
def sub2 = Mock(Subscriber)
def setup() {
pub.subscribers << sub2 << sub1
}
def "delivers messages to all subscribers"() {
when:
pub.publish("msg")
then:
1 * sub1.receive("msg")
1 * sub2.receive("msg")
}
}
Show me the code
class PublisherSubscriberSpec2 extends Specification {
def pub = new Publisher()
def sub1 = Mock(Subscriber)
def setup() {
pub.subscribers << sub1
}
def "delivers messages in the order they are published"() {
when:
pub.publish("msg1")
pub.publish("msg2")
then:
1 * sub1.receive("msg1")
then:
1 * sub1.receive("msg2")
}
}
Show me the code
class PublisherSubscriber2Spec extends Specification {
def pub = new Publisher2()
def sub1 = Mock(Subscriber2)
def sub2 = Mock(Subscriber2)
def setup() {
pub.subscribers << sub1 << sub2
}
def "delivers messages to all active subscribers"() {
sub1.active >> true
sub2.active >> false
when:
pub.publish("msg")
then:
1 * sub1.receive("msg")
0 * sub2.receive(_)
}
}
Recap: Interaction Based Testing
Creating
def sub = Mock(Subscriber)
Subscriber sub = Mock()
Mocking
1 * sub.receive("msg")
(1..3) * sub.receive(_)
(1.._) * sub.receive(_ as String)
1 * sub.receive(!null)
1 * sub.receive({it.contains("m")})
1 * _./rec.*/("msg")
Recap: Interaction Based Testing
Stubbing
// now returns status code
String receive(String msg) { ... }
sub.receive(_) >> "ok"
sub.receive(_) >>> ["ok", "ok", "fail"]
sub.receive(_) >>> { msg -> msg.size() > 3 ? "ok" : "fail" }
Mocking and Stubbing
3 * sub.receive(_) >>> ["ok", "ok", "fail"]
Impressing your friends
(_.._) * _._(*_) >> _
Spock Extensions
Spock Extensions
Listeners
Interceptors
Annotation-driven extensions
Global extensions
Built-in Extensions
@Ignore
@IgnoreRest
@FailsWith
@Timeout
@AutoCleanup
@Stepwise
@RevertMetaClass
@Rule
Show me the code
class JUnitRules extends Specification {
@Rule TemporaryFolder tempFolder
@Shared File file
def "a file based test"() {
when:
file = tempFolder.newFile("foo.txt")
then:
file.exists()
}
def "by now the file has been deleted"() {
expect:
!file.exists()
}
}
Show me the code
@Stepwise
class StepwiseSpec extends Specification {
def "step 1"() {
expect: true
}
def "step 2"() {
expect: true
}
def "step 3"() {
expect: true
}
}
External Extensions
spock-grails
spock-spring
spock-guice
spock-tapestry
spock-unitils
spock-griffon
spock-arquillian
spock-extensions http://github.com/robfletcher/spock-extensions
Grails Extensions
http://grails.org/plugin/spock
https://github.com/spockframework/spock-grails
grails install plugin spock 0.6
grails test-app
grails test-app unit:TestSpec
Grails Extensions
@TestFor(MouthService)
@Mock([Patient, PatientMouth, PatientTooth])
class MouthServiceSpec extends Specification {
def "Get patient tooth by index"() {
setup: "Create domain mocks"
def patient = new Patient(dateOfBirth: new Date('05/03/1984')).save(false)
def patientTooth = new PatientTooth(index: 10, toothCode: "10").save(false)
def patientMouth = new PatientMouth(patient: patient, patientTeeth: [patientTooth]).save(false)
when:
patientTooth = service.getPatientToothByIndex(patientMouth, index)
then:
patientTooth.toothCode == tooth
patientMouth.patientTeeth.contains(patientTooth)
where:
index | tooth
10 | "10"
20 | null
}
}
Spring Extension
class InjectionExamples extends Specification {
@Autowired
Service1 byType
@Resource
Service1 byName
@Autowired
ApplicationContext context
}
Other Cool Stuff
Configuring Spock
~/.spock/SpockConfig.groovy, or on class path, or with -Dspock.configuration
runner {
filterStackTrace false
include Fast
exclude Slow
optimizeRunOrder true
}
@Fast
class MyFastSpec extends Specification {
def "I’m fast as hell!"() { expect: true }
@Slow
def “Sorry, can’t keep up..."() {expect: false }
}
Tooling
Eclipse, IDEA
Ant, Maven, Gradle
Jenkins, Bamboo, TeamCity
Spock runs everywhere JUnit and Groovy run!
Spock Under The Hood
You write...
a = 1; b = 2; c = 4
expect: sum(a, b) == c
Spock generates...
def rec = new ValueRecorder()
verifier.expect(
rec.record(rec.record(sum(rec.record(a),
rec.record(b)) == rec.record(c)))
You see...
sum (a, b) == c
| | | | |
3 1 2 | 4
false
Q&A
Homepage
http://spockframework.org
Source Code
https://github.com/spockframework/spock
Spock Web Console
http://meet.spockframework.org
Spock Example Project
http://downloads.spockframework.org
https://github.com/spockframework/spock/tree/groovy-1.8/spock-example

Smarter Testing with Spock

  • 1.
  • 2.
    What we’ll talkabout Spock?! State Based Testing Data Driven Testing Interaction Based Testing Spock Extensions More Cool Stuff
  • 3.
  • 4.
    Spock is... A developertesting framework... for Groovy and Java applications... based on Groovy... fully compatible with JUnit... but going beyond!
  • 5.
    Spock Can... Reduce thelines of test code Make tests more readable Turn tests into specifications Be extended in powerful ways Bring back the fun to testing!
  • 6.
    Getting Started Homepage http://spockframework.org Source Code https://github.com/spockframework/spock SpockWeb Console http://meet.spockframework.org Spock Example Project http://downloads.spockframework.org https://github.com/spockframework/spock/tree/groovy-1.8/spock- example
  • 7.
    Who’s Using Spock? GradleGPars Grails Geb Grails Plugin Collective Apache Tapestry Griffon Spock Spring?
  • 8.
    Who’s Using Spock? Betfairbe2 bemoko BSkyB CTI Digital Donewtech Solutions eHarmony Energized Work Gennemtænkt IT IntelliGrape Software Netflix Smarter Ecommerce STERMEDIA Sp. z o.o. Software Projects Spot Diving SystemsForge TouK
  • 9.
  • 10.
    State Based Testing ClassicalUnit Testing Arrange Act Assert Given-When-Then
  • 11.
    The Code ForTesting Account.java public class Account { private BigDecimal balance = new BigDecimal(0); public Account(BigDecimal initial) { balance = initial; } public BigDecimal getBalance() { return balance; } public void withdraw(BigDecimal amount) { if (amount.compareTo(BigDecimal.ZERO) < 0) { throw new NegativeAmountWithdrawnException(amount); } balance = balance.subtract(amount); } }
  • 12.
    The Code ForTesting NegativeAmountWithdrawnException.java public class NegativeAmountWithdrawnException extends RuntimeException { private final BigDecimal amount; public NegativeAmountWithdrawnException(BigDecimal amount) { super("cannot withdraw" + amount); this.amount = amount; } public BigDecimal getAmount() { return amount; } }
  • 13.
    Show me thecode public class AccountTest { @Test public void withdrawSomeAmount() { // given Account account = new Account(BigDecimal.valueOf(5)); // when account.withdraw(BigDecimal.valueOf(2)); // then assertEquals(BigDecimal.valueOf(3), account.getBalance()); } }
  • 14.
    Show me thecode class AccountSpec extends Specification { def "withdraw some amount"() { given: Account account = new Account(BigDecimal.valueOf(5)); when: account.withdraw(BigDecimal.valueOf(2)); then: account.getBalance() == BigDecimal.valueOf(3); } }
  • 15.
    Show me thecode class AccountSpec2 extends Specification { def "withdraw some amount"() { given: def account = new Account(5.0) when: account.withdraw(2.0) then: account.balance == 3.0 } }
  • 16.
    Show me thecode class AccountSpec3 extends Specification { def "withdraw some amount"() { given: "an account with a balance of five euros" def account = new Account(5.0) when: "two euros are withdrawn" account.withdraw(2.0) then: "three euros remain in the account" account.balance == 3.0 } }
  • 17.
    Show me thecode class AccountSpec4 extends Specification { def "can't withdraw a negative amount"() { given: def account = new Account(5.0) when: account.withdraw(-1.0) then: NegativeAmountWithdrawnException e = thrown() e.amount == -1.0 } def "withdrawing some amount decreases the balance by exactly that amount"() { def account = new Account(5.0) when: account.withdraw(2.0) then: account.balance == old(account.balance) - 2.0 } }
  • 18.
    Show me thecode class SharedField extends Specification { @Shared File file def "a file based test"() { when: file = new File("foo.txt") file.createNewFile() then: file.exists() } def "by now the object is not null"() { expect: file.exists() cleanup: file.delete() } }
  • 19.
    Show me thecode class SetupSpecSpec extends Specification { File file def setup() { file = new File("foo2.txt") } def "a file based test"() { when: file.createNewFile() then: file.exists() } def "by now the object is not null"() { expect: file.exists() cleanup: file.delete() } }
  • 20.
    Show me thecode class Conditions extends Specification { def "when-then style"() { when: def x = Math.max(5, 9) then: x == 9 } def "expect style"() { expect: Math.max(5, 9) == 9 } def "more complex conditions"() { expect: germanCarBrands.any { it.size() >= 3 } } private getGermanCarBrands() { ["audi", "bmw", "porsche"] } }
  • 21.
    Recap: State BasedTesting Blocks setup: cleanup: expect: given: when: then: where: and: Fixture Methods setup() cleanup() setupSpec() cleanupSpec() Instance and @Shared fields old() and thrown()
  • 22.
  • 23.
    Data Driven Testing Testthe same behavior... with varying data!
  • 24.
    Show me thecode class AccountSpecDataDriven extends Specification { @Unroll def "withdraw some amount"() { given: def account = new Account(balance) when: account.withdraw(withdrawn) then: account.balance == remaining where: balance | withdrawn | |remaining 5.0 | 2.0 | | 3.0 4.0 | 0.0 | | 4.0 4.0 | 4.0 | | 0.0 } }
  • 25.
    Show me thecode class AccountSpecDataDriven extends Specification { @Unroll def "withdrawing #withdrawn from account with balance #balance"() { given: def account = new Account(balance) when: account.withdraw(withdrawn) then: account.balance == old(account.balance) – withdrawn where: balance | withdrawn 5.0 | 2.0 4.0 | 0.0 4.0 | 4.0 } }
  • 26.
    Show me thecode class AccountSpecDatabaseDriven extends Specification { @Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver") def setupSpec() { sql.execute("create table accountdata (id int primary key, balance decimal, withdrawn decimal, remaining decimal)") sql.execute("insert into accountdata values (1, 5.0, 2.0, 3.0), (2, 4.0, 0.0, 4.0), (3, 4.0, 4.0, 0.0)") } @Unroll def "withdrawing #withdrawn from account with balance #balance leaves #remaining"() { given: def account = new Account(balance) when: account.withdraw(withdrawn) then: account.balance == remaining where: [balance, withdrawn, remaining] << sql.rows("select balance, withdrawn, remaining from accountdata") } }
  • 27.
    Recap: Data DrivenTesting where: block Data tables External data sources @Unroll
  • 28.
  • 29.
    Interaction Based Testing Designand test how your objects communicate Mocking frameworks to the rescue Spock comes with its own mocking framework
  • 30.
    The Code ForTesting PublisherSubscriber.groovy interface Subscriber { void receive(String message) } class Publisher { List<Subscriber> subscribers = [] void publish(String message) { for (subscriber in subscribers) { try { subscriber.receive(message) } catch (ignored) {} } } }
  • 31.
    The Code ForTesting PublisherSubscriber.groovy interface Subscriber2 { void receive(String message) boolean isActive() } class Publisher2 { List<Subscriber2> subscribers = [] void publish(String message) { for (subscriber in subscribers) { try { if (subscriber.active) { subscriber.receive(message) } } catch (ignored) {} } } }
  • 32.
    Show me thecode class PublisherSubscriberSpec extends Specification { def pub = new Publisher() def sub1 = Mock(Subscriber) def sub2 = Mock(Subscriber) def setup() { pub.subscribers << sub2 << sub1 } def "delivers messages to all subscribers"() { when: pub.publish("msg") then: 1 * sub1.receive("msg") 1 * sub2.receive("msg") } }
  • 33.
    Show me thecode class PublisherSubscriberSpec2 extends Specification { def pub = new Publisher() def sub1 = Mock(Subscriber) def setup() { pub.subscribers << sub1 } def "delivers messages in the order they are published"() { when: pub.publish("msg1") pub.publish("msg2") then: 1 * sub1.receive("msg1") then: 1 * sub1.receive("msg2") } }
  • 34.
    Show me thecode class PublisherSubscriber2Spec extends Specification { def pub = new Publisher2() def sub1 = Mock(Subscriber2) def sub2 = Mock(Subscriber2) def setup() { pub.subscribers << sub1 << sub2 } def "delivers messages to all active subscribers"() { sub1.active >> true sub2.active >> false when: pub.publish("msg") then: 1 * sub1.receive("msg") 0 * sub2.receive(_) } }
  • 35.
    Recap: Interaction BasedTesting Creating def sub = Mock(Subscriber) Subscriber sub = Mock() Mocking 1 * sub.receive("msg") (1..3) * sub.receive(_) (1.._) * sub.receive(_ as String) 1 * sub.receive(!null) 1 * sub.receive({it.contains("m")}) 1 * _./rec.*/("msg")
  • 36.
    Recap: Interaction BasedTesting Stubbing // now returns status code String receive(String msg) { ... } sub.receive(_) >> "ok" sub.receive(_) >>> ["ok", "ok", "fail"] sub.receive(_) >>> { msg -> msg.size() > 3 ? "ok" : "fail" } Mocking and Stubbing 3 * sub.receive(_) >>> ["ok", "ok", "fail"] Impressing your friends (_.._) * _._(*_) >> _
  • 37.
  • 38.
  • 39.
  • 40.
    Show me thecode class JUnitRules extends Specification { @Rule TemporaryFolder tempFolder @Shared File file def "a file based test"() { when: file = tempFolder.newFile("foo.txt") then: file.exists() } def "by now the file has been deleted"() { expect: !file.exists() } }
  • 41.
    Show me thecode @Stepwise class StepwiseSpec extends Specification { def "step 1"() { expect: true } def "step 2"() { expect: true } def "step 3"() { expect: true } }
  • 42.
  • 43.
  • 44.
    Grails Extensions @TestFor(MouthService) @Mock([Patient, PatientMouth,PatientTooth]) class MouthServiceSpec extends Specification { def "Get patient tooth by index"() { setup: "Create domain mocks" def patient = new Patient(dateOfBirth: new Date('05/03/1984')).save(false) def patientTooth = new PatientTooth(index: 10, toothCode: "10").save(false) def patientMouth = new PatientMouth(patient: patient, patientTeeth: [patientTooth]).save(false) when: patientTooth = service.getPatientToothByIndex(patientMouth, index) then: patientTooth.toothCode == tooth patientMouth.patientTeeth.contains(patientTooth) where: index | tooth 10 | "10" 20 | null } }
  • 45.
    Spring Extension class InjectionExamplesextends Specification { @Autowired Service1 byType @Resource Service1 byName @Autowired ApplicationContext context }
  • 46.
  • 47.
    Configuring Spock ~/.spock/SpockConfig.groovy, oron class path, or with -Dspock.configuration runner { filterStackTrace false include Fast exclude Slow optimizeRunOrder true } @Fast class MyFastSpec extends Specification { def "I’m fast as hell!"() { expect: true } @Slow def “Sorry, can’t keep up..."() {expect: false } }
  • 48.
    Tooling Eclipse, IDEA Ant, Maven,Gradle Jenkins, Bamboo, TeamCity Spock runs everywhere JUnit and Groovy run!
  • 49.
    Spock Under TheHood You write... a = 1; b = 2; c = 4 expect: sum(a, b) == c Spock generates... def rec = new ValueRecorder() verifier.expect( rec.record(rec.record(sum(rec.record(a), rec.record(b)) == rec.record(c))) You see... sum (a, b) == c | | | | | 3 1 2 | 4 false
  • 50.
    Q&A Homepage http://spockframework.org Source Code https://github.com/spockframework/spock Spock WebConsole http://meet.spockframework.org Spock Example Project http://downloads.spockframework.org https://github.com/spockframework/spock/tree/groovy-1.8/spock-example