smarter testing with spock
TRANSCRIPT
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 StartedHomepage 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 Extensionclass 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
Q&AHomepagehttp://spockframework.org
Source Codehttps://github.com/spockframework/spock
Spock Web Consolehttp://meet.spockframework.org
Spock Example Projecthttp://downloads.spockframework.orghttps://github.com/spockframework/spock/tree/groovy-1.8/spock-example