smarter testing with spock

Post on 10-May-2015

2.835 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

by Dmitry Voloshko What we’ll talk about Spock?! State Based Testing Data Driven Testing Interaction Based Testing Spock Extensions More Cool Stuff

TRANSCRIPT

Dmitry Voloshko03/11/2012

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.orghttps://github.com/spockframework/spock/tree/groovy-1.8/spock-example

Who’s Using Spock?Gradle GPars

Grails Geb

Grails Plugin Collective

Apache Tapestry

Griffon SpockSpring?

Who’s Using Spock? Betfair be2

bemoko BSkyB CTI DigitalDonewtech Solutions

eHarmony Energized Work Gennemtænkt IT

IntelliGrape Software NetflixSmarter Ecommerce STERMEDIA Sp. z o.o.

Software Projects Spot DivingSystemsForge TouK

State Based Testing

State Based Testing

Classical Unit TestingArrangeActAssert

Given-When-Then

The Code For TestingAccount.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 TestingNegativeAmountWithdrawnException.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 codepublic 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 codeclass 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 codeclass 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 codeclass 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 codeclass 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 codeclass 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 codeclass 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 codeclass 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 TestingBlockssetup: cleanup: expect: given: when: then:where: and:

Fixture Methodssetup() 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 codeclass 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 codeclass 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 codeclass 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 objectscommunicate

Mocking frameworks to the rescue

Spock comes with its own mocking framework

The Code For TestingPublisherSubscriber.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 TestingPublisherSubscriber.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 codeclass 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 codeclass 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 codeclass 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

Creatingdef sub = Mock(Subscriber)Subscriber sub = Mock()

Mocking1 * 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 codeString receive(String msg) { ... }

sub.receive(_) >> "ok"sub.receive(_) >>> ["ok", "ok", "fail"]sub.receive(_) >>> { msg -> msg.size() > 3 ? "ok" : "fail" }

Mocking and Stubbing3 * 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 codeclass 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@Stepwiseclass 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 falseinclude Fastexclude SlowoptimizeRunOrder true

}

@Fastclass 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 HoodYou write...a = 1; b = 2; c = 4expect: 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

top related