Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

7 Reasons Why Bother Learning Spock

7 Reasons Why Bother Learning Spock

JUnit has been around for over a decade. Enhanced with Mockito and AssertJ it still can be used to write decent automatic tests, however, there is a better way. Thanks to Spock Framework tests/specifications can be written faster in shorter and more readable form. During my talk I will present 7 opinionated reasons why it is worth learning Spock to bring your automatic tests to the next level. This presentation is targeted at Java developers who care about writing automatic tests (all of us?) and for some reasons haven't decided to give Spock a shot yet.

Marcin Zajączkowski

September 06, 2016
Tweet

More Decks by Marcin Zajączkowski

Other Decks in Programming

Transcript

  1. About me Areas of expertise Automatic Testing / TDD (with

    Spock of course :) ) Software Craftsmanship / Code Quality Concurrency / Parallel Computing / Reactive Systems Deployment Automation / Continuous Delivery FOSS projects author and contributor, blogger, trainer CTO of small software house - Codearte targeted at clients who care about the quality Trainer in Bottega IT Solutions
  2. BDD specification by default class SimpleCalculatorSpec extends Specification { def

    "should sum two numbers"() { given: Calculator calculator = new SimpleCalculator() when: int result = calculator.sum(1, 2) then: result == 3 } }
  3. BDD specification by default class SimpleCalculatorSpec extends Specification { def

    "should sum two numbers"() { given: Calculator calculator = new SimpleCalculator() when: int result = calculator.sum(1, 2) then: result == 3 } [given]/when/then (or expect) are required to compile code not just comments in code
  4. Power Assertions - basic case reused Java assert keyword assert

    (2 + 3) * 4 != (2 * 4) + (3 * 4) self explaining reason of failure Assertion failed: assert (2 + 3) * 4 != (2 * 4) + (3 * 4) | | | | | | 5 20 false 8 20 12
  5. Power Assertions - more complex case not only mathematical expressions

    String word = "Spock" int begin = 1 int end = 3 assert word.substring(begin, end) == word[begin..end]
  6. Power Assertions - more complex case not only mathematical expressions

    String word = "Spock" int begin = 1 int end = 3 assert word.substring(begin, end) == word[begin..end] also for method return types and arguments Assertion failed: assert word.substring(begin, end) == word[begin..end] | | | | | | || | | po 1 3 | | |1 3 Spock | | poc | Spock false
  7. Power Assertions - other complex case even for complicated structures

    and expressions assert ann.name == bob.name && ann.age == bob.age
  8. Power Assertions - other complex case even for complicated structures

    and expressions assert ann.name == bob.name && ann.age == bob.age detailed evaluation of sub elements Assertion failed: assert ann.name == bob.name && ann.age == bob.age | | | | | | | Ann | | Bob false | | Person(name:Bob, age:7) | false Person(name:Ann, age:4)
  9. Power Assertions - other complex case even for complicated structures

    and expressions assert ann.name == bob.name && ann.age == bob.age detailed evaluation of sub elements Assertion failed: assert ann.name == bob.name && ann.age == bob.age | | | | | | | Ann | | Bob false | | Person(name:Bob, age:7) | false Person(name:Ann, age:4) second part ignored as meaningless
  10. Power Assertions self-explaining optional assert keyword in then and expect

    code block unless placed in Closure or separate method backported to Groovy
  11. Parameterized tests def "should sum two integers"() { given: Calculator

    calculator = new SimpleCalculator() when: int result = calculator.add(x, y) then: result == expectedResult where: x | y || expectedResult 1 | 2 || 3 -2 | 3 || 1 -1 | -2 || -3 }
  12. Parameterized tests def "should sum two integers"() { given: Calculator

    calculator = new SimpleCalculator() when: int result = calculator.add(x, y) then: result == expectedResult where: x | y || expectedResult 1 | 2 || 3 -2 | 3 || 1 -1 | -2 || -3 } build-in support with where keyword does not stop on failure for given test case syntactic sugar for table-like data formatting
  13. Parameterized tests - even better @Unroll def "should sum two

    integers (#x + #y = #expectedResult)"() { given: Calculator calculator = new SimpleCalculator() when: int result = calculator.add(x, y) then: result == expectedResult where: x | y || expectedResult 1 | 2 || 3 -2 | 3 || 1 -1 | -2 || -3 } separate test for every input parameters set - @Unroll visible also in reports and IDE input parameters presented in test name (with #) data pipes and data providers for advanced use cases
  14. Simple Stubbing class DaoSpec extends Specification { def "should stub

    method call"() { given: Dao dao = Stub() dao.getCount() >> 1 expect: dao.getCount() == 1 } } interface Dao { int getCount() Item save(Item item) }
  15. Simple Stubbing - custom logic class DaoSpec extends Specification {

    def "should throw exception for specific input parameters"() { given: Dao<Item> dao = Stub() dao.save(_) >> { Item item -> throw new IllegalArgumentException(item.toString()) } when: dao.save(new Item()) then: thrown(IllegalArgumentException) } } _ for any argument value arguments available inside Closure
  16. Mock interactions verification class DaoSpec extends Specification { def "should

    stub and verify"() { given: Item baseItem = new Item() and: Dao<Item> dao = Mock() when: dao.delete(baseItem) then: 1 * dao.delete(_) } } 1* - method called once (_) - with any value as the first parameter
  17. Stubbing and verifying together class DaoSpec extends Specification { def

    "should stub and verify"() { given: Item baseItem = new Item() and: Dao<Item> dao = Mock() when: Item returnedItem = dao.save(baseItem) then: 1 * dao.save(_) >> { Item item -> item } and: baseItem.is(returnedItem) } } has to be defined in the same statement in then section
  18. Stubbing and verifying together class DaoSpec extends Specification { def

    "should stub and verify"() { given: Item baseItem = new Item() and: Dao<Item> dao = Mock() when: Item returnedItem = dao.save(baseItem) then: 1 * dao.save(_) >> { Item item -> item } and: baseItem.is(returnedItem) } } has to be defined in the same statement in then section design suggestion: in most cases stubbing and interaction verification of the same mock shouldn't be needed
  19. Capturing thrown exception def "should capture exception"() { when: throwNPE()

    then: NullPointerException e = thrown() e.message == "test NPE" }
  20. Capturing thrown exception def "should capture exception"() { when: throwNPE()

    then: NullPointerException e = thrown() e.message == "test NPE" } thrown exception intercepted and assigned to variable for further asserting test failed if not thrown or has unexpected type Exceptions utility class to play with cause chain
  21. Groovy - why bother? smarter, shorten, more powerful Java Closure

    to make functions first-class citizen Java code (in most cases) is also valid Groovy code flat learning curve seamlessly integration with Java code can use Java libraries
  22. Groovy - lists and sets compact syntax for list and

    set creation List<String> names = ['Ann', 'Bob', 'Monica', 'Scholastica'] Set<Integer> luckyNumbers = [4, 7, 9, 7] as Set //4, 7, 9
  23. Groovy - lists and sets compact syntax for list and

    set creation List<String> names = ['Ann', 'Bob', 'Monica', 'Scholastica'] Set<Integer> luckyNumbers = [4, 7, 9, 7] as Set //4, 7, 9 accessing String secondName = names[1] //Bob String lastName = names[-1] //Scholastica
  24. Groovy - lists and sets compact syntax for list and

    set creation List<String> names = ['Ann', 'Bob', 'Monica', 'Scholastica'] Set<Integer> luckyNumbers = [4, 7, 9, 7] as Set //4, 7, 9 accessing String secondName = names[1] //Bob String lastName = names[-1] //Scholastica modification names[1] = 'Alex' //Ann, Alex, Monica, Scholastica names << 'John' //Ann, Alex, Monica, Scholastica, John Set<Integer> withoutSeven = luckyNumbers - 7 //4, 9
  25. Groovy - maps compact syntax for map creation Map<String, Integer>

    childrenWithAge = [Ann: 5, Bob: 7, Monica: 9, Scholastica: 7]
  26. Groovy - maps compact syntax for map creation Map<String, Integer>

    childrenWithAge = [Ann: 5, Bob: 7, Monica: 9, Scholastica: 7] accessing childrenWithAge['Ann'] //5
  27. Groovy - maps compact syntax for map creation Map<String, Integer>

    childrenWithAge = [Ann: 5, Bob: 7, Monica: 9, Scholastica: 7] accessing childrenWithAge['Ann'] //5 modification childrenWithAge['Bob'] = 8 //Ann: 5, Bob: 8, Monica: 9, Scholastica: 7 Map<String, Integer> withAlice = childrenWithAge + [Alice: 3] //Ann: 5, Bob: 8, Monica: 9, Scholastica: 7, Alice: 3
  28. Functional Groovy - Closures operations on collection List<String> names =

    ['Ann', 'Bob', 'Monica', 'Scholastica'] names.findAll { String name -> name.length() > 3 } //Monica, Scholastica
  29. Functional Groovy - Closures operations on collection List<String> names =

    ['Ann', 'Bob', 'Monica', 'Scholastica'] names.findAll { String name -> name.length() > 3 }.collect { String name -> //can be chained name.toUpperCase() } //MONICA, SCHOLASTICA
  30. Functional Groovy - Closures operations on collection List<String> names =

    ['Ann', 'Bob', 'Monica', 'Scholastica'] names.findAll { String name -> name.length() > 3 }.collect { String name -> //can be chained name.toUpperCase() } //MONICA, SCHOLASTICA it to refers Closure execution argument - in simple cases Set<Integer> luckyNumbers = [4, 7, 9, 7] as Set luckyNumbers.findAll { it % 2 == 0 } //4
  31. Functional Groovy - Closures inlined functional interfaces //production Java method

    to call void executeMultipleTimes(int number, Runnable codeToExecute); executeMultipleTimes(5, { println "Executed" }) //or simplier executeMultipleTimes(5) { println "Executed" }
  32. (G)Strings variable reference void printMagicNumber(int number) { println "Today magic

    number is $number. Congrats!" } method execution println "Milliseconds since the epoch: ${System.currentTimeMillis()}."
  33. (G)Strings variable reference void printMagicNumber(int number) { println "Today magic

    number is $number. Congrats!" } method execution println "Milliseconds since the epoch: ${System.currentTimeMillis()}." multi-line string String mailBody = """ Hello User, Welcome to our newsletter. Have a good day!"""
  34. (G)Strings variable reference void printMagicNumber(int number) { println "Today magic

    number is $number. Congrats!" } method execution println "Milliseconds since the epoch: ${System.currentTimeMillis()}." multi-line string String mailBody = """ Hello User, Welcome to our newsletter. Have a good day!""".stripIndent()
  35. Extensibility very powerful extensions mechanism dozens of internal features implemented

    as extensions provided out-of-box @AutoCleanup, @IgnoreIf, @RestoreSystemProperties, ... many extensions as external projects ability to reuse JUnit's @Rule and @ClassRule
  36. Why Spock? consist and readable test code tests as specification

    by default all Groovy magic available to help chance to learn new language embedded mocking framework although Mockito can be used if preferred (or needed) highly extensible compatible with tools supporting JUnit
  37. What's next? in most cases it is worth to give

    Spock a try and fall in love with its readability and simplicity
  38. What's next? in most cases it is worth to give

    Spock a try and fall in love with its readability and simplicity let's make a small experiment :) write all new tests in your team/project entirely in Spock for a week decide if you liked it tip: sample Spock configuration for Gradle and Maven available on my blog