Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Smarter Testing With Spock

5,844 views

Published on

by Dmitry Voloshko

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

Smarter Testing With Spock

  1. 1. Smarter Testing With Spock Dmitry Voloshko 03/11/2012
  2. 2. What we’ll talk aboutSpock?!State Based TestingData Driven TestingInteraction Based TestingSpock ExtensionsMore Cool Stuff
  3. 3. Spock?!
  4. 4. Spock is...A developer testing framework...for Groovy and Java applications...based on Groovy...fully compatible with JUnit...but going beyond!
  5. 5. Spock Can...Reduce the lines of test codeMake tests more readableTurn tests into specificationsBe extended in powerful waysBring back the fun to testing!
  6. 6. Getting StartedHomepagehttp://spockframework.orgSource Codehttps://github.com/spockframework/spockSpock Web Consolehttp://meet.spockframework.orgSpock Example Projecthttp://downloads.spockframework.orghttps://github.com/spockframework/spock/tree/groovy-1.8/spock-example
  7. 7. Who’s Using Spock? Gradle GPars Grails Geb Grails Plugin Collective Apache Tapestry Griffon Spock Spring?
  8. 8. Who’s Using Spock? Betfair be2 bemoko BSkyB CTI Digital Donewtech Solutions eHarmonyEnergized Work Gennemtænkt IT IntelliGrape Software NetflixSmarter Ecommerce STERMEDIA Sp. z o.o. Software Projects Spot Diving SystemsForge TouK
  9. 9. State Based Testing
  10. 10. State Based TestingClassical Unit Testing Arrange Act AssertGiven-When-Then
  11. 11. The Code For TestingAccount.javapublic 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. 12. The Code For TestingNegativeAmountWithdrawnException.javapublic 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. 13. 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()); }}
  14. 14. 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); }}
  15. 15. 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 }}
  16. 16. 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 }}
  17. 17. Show me the codeclass AccountSpec4 extends Specification { def "cant 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. 18. 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() }}
  19. 19. 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() }}
  20. 20. 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"] }}
  21. 21. Recap: State Based TestingBlockssetup: cleanup: expect: given: when: then:where: and:Fixture Methodssetup() cleanup() setupSpec() cleanupSpec()Instance and @Shared fieldsold() and thrown()
  22. 22. Data Driven Testing
  23. 23. Data Driven TestingTest the same behavior...with varying data!
  24. 24. 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 }}
  25. 25. 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 }}
  26. 26. 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") }}
  27. 27. Recap: Data Driven Testingwhere: blockData tablesExternal data sources@Unroll
  28. 28. Interaction Based Testing
  29. 29. Interaction Based TestingDesign and test how your objectscommunicateMocking frameworks to the rescueSpock comes with its own mockingframework
  30. 30. The Code For TestingPublisherSubscriber.groovyinterface 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. 31. The Code For TestingPublisherSubscriber.groovyinterface 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. 32. 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") }}
  33. 33. 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") }}
  34. 34. 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(_) }}
  35. 35. 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")
  36. 36. 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 (_.._) * _._(*_) >> _
  37. 37. Spock Extensions
  38. 38. Spock ExtensionsListenersInterceptorsAnnotation-driven extensionsGlobal extensions
  39. 39. Built-in Extensions@Ignore@IgnoreRest@FailsWith@Timeout@AutoCleanup@Stepwise@RevertMetaClass@Rule
  40. 40. 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() }}
  41. 41. Show me the code@Stepwiseclass StepwiseSpec extends Specification { def "step 1"() { expect: true } def "step 2"() { expect: true } def "step 3"() { expect: true }}
  42. 42. External Extensionsspock-grailsspock-springspock-guicespock-tapestryspock-unitilsspock-griffonspock-arquillianspock-extensions http://github.com/robfletcher/spock-extensions
  43. 43. Grails Extensionshttp://grails.org/plugin/spockhttps://github.com/spockframework/spock-grailsgrails install plugin spock 0.6grails test-appgrails test-app unit:TestSpec
  44. 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. 45. Spring Extensionclass InjectionExamples extends Specification { @Autowired Service1 byType @Resource Service1 byName @Autowired ApplicationContext context}
  46. 46. Other Cool Stuff
  47. 47. Configuring Spock~/.spock/SpockConfig.groovy, or on class path, or with -Dspock.configurationrunner { filterStackTrace false include Fast exclude Slow optimizeRunOrder true}@Fastclass MyFastSpec extends Specification { def "I’m fast as hell!"() { expect: true } @Slow def “Sorry, can’t keep up..."() {expect: false }}
  48. 48. ToolingEclipse, IDEAAnt, Maven, GradleJenkins, Bamboo, TeamCitySpock runs everywhere JUnit and Groovy run!
  49. 49. Spock Under The HoodYou write...a = 1; b = 2; c = 4expect: sum(a, b) == cSpock 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 12 | 4 false
  50. 50. Q&AHomepagehttp://spockframework.orgSource Codehttps://github.com/spockframework/spockSpock Web Consolehttp://meet.spockframework.orgSpock Example Projecthttp://downloads.spockframework.orghttps://github.com/spockframework/spock/tree/groovy-1.8/spock-example

×