Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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. 7 reasons why bother learning Spock
    (for Java developers)
    Riga, 6th September 2016

    View full-size slide

  2. 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

    View full-size slide

  3. Why bother learning Spock?

    View full-size slide

  4. Reason 1
    BDD specification by default

    View full-size slide

  5. 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
    }
    }

    View full-size slide

  6. 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

    View full-size slide

  7. Reason 2
    Power Assertions

    View full-size slide

  8. Power Assertions - basic case
    reused Java assert keyword
    assert (2 + 3) * 4 != (2 * 4) + (3 * 4)

    View full-size slide

  9. 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

    View full-size slide

  10. 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]

    View full-size slide

  11. 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

    View full-size slide

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

    View full-size slide

  13. 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)

    View full-size slide

  14. 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

    View full-size slide

  15. Power Assertions
    self-explaining
    optional assert keyword in then and expect code block
    unless placed in Closure or separate method
    backported to Groovy

    View full-size slide

  16. Reason 3
    First class support
    for parameterized tests

    View full-size slide

  17. 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
    }

    View full-size slide

  18. 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

    View full-size slide

  19. 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

    View full-size slide

  20. Reason 4
    Built-in mocking framework

    View full-size slide

  21. 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)
    }

    View full-size slide

  22. Simple Stubbing - custom logic
    class DaoSpec extends Specification {
    def "should throw exception for specific input parameters"() {
    given:
    Dao 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

    View full-size slide

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

    View full-size slide

  24. Stubbing and verifying together
    class DaoSpec extends Specification {
    def "should stub and verify"() {
    given:
    Item baseItem = new Item()
    and:
    Dao 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

    View full-size slide

  25. Stubbing and verifying together
    class DaoSpec extends Specification {
    def "should stub and verify"() {
    given:
    Item baseItem = new Item()
    and:
    Dao 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

    View full-size slide

  26. Reason 5
    Exception testing

    View full-size slide

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

    View full-size slide

  28. 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

    View full-size slide

  29. Reason 6
    Groovy magic

    View full-size slide

  30. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  33. Groovy - lists and sets
    compact syntax for list and set creation
    List names = ['Ann', 'Bob', 'Monica', 'Scholastica']
    Set 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 withoutSeven = luckyNumbers - 7 //4, 9

    View full-size slide

  34. Groovy - maps
    compact syntax for map creation
    Map childrenWithAge =
    [Ann: 5, Bob: 7, Monica: 9, Scholastica: 7]

    View full-size slide

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

    View full-size slide

  36. Groovy - maps
    compact syntax for map creation
    Map 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 withAlice = childrenWithAge + [Alice: 3]
    //Ann: 5, Bob: 8, Monica: 9, Scholastica: 7, Alice: 3

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  39. Functional Groovy - Closures
    operations on collection
    List 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 luckyNumbers = [4, 7, 9, 7] as Set
    luckyNumbers.findAll { it % 2 == 0 } //4

    View full-size slide

  40. 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" }

    View full-size slide

  41. (G)Strings
    variable reference
    void printMagicNumber(int number) {
    println "Today magic number is $number. Congrats!"
    }

    View full-size slide

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

    View full-size slide

  43. (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!"""

    View full-size slide

  44. (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()

    View full-size slide

  45. Reason 7
    Extensibility

    View full-size slide

  46. 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

    View full-size slide

  47. 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

    View full-size slide

  48. What's next?

    View full-size slide

  49. What's next?
    in most cases it is worth to give Spock a try

    View full-size slide

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

    View full-size slide

  51. 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

    View full-size slide

  52. Questions?
    Marcin Zajączkowski
    http://blog.solidsoft.info/
    @SolidSoftBlog
    [email protected]

    View full-size slide

  53. Thank you!
    Marcin Zajączkowski
    http://blog.solidsoft.info/
    @SolidSoftBlog
    [email protected]

    View full-size slide