Spock



Spock

0 0


jug-spock-talk

JUG (http://www.juggraz.at/) - Talk on Spock

On Github zupzup / jug-spock-talk

Spock

Mario Zupan - @mzupzup

Hello, I'm talking about the Spock testing framework today

About me

  • @netconomy for 4 years
  • ~2.5 years of Java
  • ~1.5 years of JavaScript
  • Software Craftsmanship
intro NC Passionate about Software Craftsmanship founded clan, advocate for code quality and testing

Agenda

  • Overview of Spock's Capabilities
  • Code-Examples
  • How to use it
  • Impulse to try it
overview, not in-depth guide used it on 2 production projects best testing framework java examples and how to get started motivate to try it out

Spock

Spock named after spock it also means Specification - Mock

Spock Facts

  • around since 2008
  • 1.0 just got released
  • created by an Austrian (@pniederw)
  • It uses Groovy!
1.0 released at the start of march created by peter niederwieser, an austrian, linz, gradleware used by companies like Netflix, JFrog, Zeroturnaround projects like grails, asgard, gradle
  • Simple
  • Syntactic Sugar
  • Functional Features
  • List / Map Literals
  • ...
why is groovy a good thing? it's simple, it provides lots of great syntactic sugar for concise, easy to reuse code closures, literals etc. it's a nice fit for tooling and tests also, metaprogramming adds a lot of power

Spock Features

  • Learning Curve
  • Unrolling
  • Data-Driven Testing
  • Interaction Based Testing
  • Framework Integration
  • DSL
it is really easy to learn it supports different ways of testing and great expressiveness as well as great integration with frameworks arguably the most amazing feature is it's amazing DSL for writing tests

Test Class

import spock.lang.*;
class HelloSpock extends Specification {
    ...
}
a spock test class starts like this

Test Case

import spock.lang.*;
class HelloSpock extends Specification {
    def "adds two numbers" () {
        ...
    }
}
test cases are named like this

Whole Test

import spock.lang.*;
class HelloSpock extends Specification {
    def "adds two numbers" () {
        expect:
        add(a, b) == c

        where:
        a | b || c
        1 | 7 || 8
        4 | 4 || 8
        7 | 1 || 8
    }
}
this is a whole test with an expect and where block showcasing data tables which we'll get to later this is the expect/where syntax

Blocks - Setup

import spock.lang.*;
class HelloSpock extends Specification {
    def "adds to the wallet" () {
        setup:
        Wallet wallet = new Wallet()
    }
}
setup is used to create objects, mocks, spies, stubs etc.

Blocks - Given

import spock.lang.*;
class HelloSpock extends Specification {
    def "adds to the wallet" () {
        given:
        Wallet wallet = new Wallet()
    }
}
given is the same as setup with behaviour driven syntax

Blocks - When

import spock.lang.*;
class HelloSpock extends Specification {
    def "adds to the wallet" () {
        given:
        Wallet wallet = new Wallet()

        when:
        wallet.add(100)
    }
}
the when-block describes the stimulus of the test the action you really want to test

Blocks - Then

import spock.lang.*;
class HelloSpock extends Specification {
    def "adds to the wallet" () {
        given:
        Wallet wallet = new Wallet()

        when:
        wallet.add(100)

        then:
        wallet.get() == 100
    }
}
the then-block is the response to the stimulus, where conditions, exceptions and interactions are evaluated

Blocks - And

import spock.lang.*;
class HelloSpock extends Specification {
    def "adds to the wallet" () {
        given: "there is a wallet"
        Wallet wallet = new Wallet()

        when: "100 euros are added"
        wallet.add(100)

        and: "another 100 euros are added"
        wallet.add(100)

        then: "there are 200 euros in the wallet"
        wallet.get() == 200
    }
}
the and-block is just for documentation, all blocks can be documented

Blocks - Cleanup

import spock.lang.*;
class HelloSpock extends Specification {
    def "adds to the wallet" () {
        given:
        Wallet wallet = new Wallet()

        when:
        wallet.add(100)

        then:
        wallet.get() == 100

        cleanup:
        wallet.delete()
    }
}
the cleanup method does exactly what is expected

Blocks - Where

import spock.lang.*;
class HelloSpock extends Specification {
    def "computing the maximum of two numbers"() {
      expect:
      Math.max(a, b) == c

      where:
      a << [5, 3]
      b << [1, 9]
      c << [5, 9]
    }
}
where is always last in a test method and used for data tables this test for example, creates two versions of the test a = 5, then 3 b = 1, then 9 c = 5, then 9

Failure Reporting

Condition not satisfied:

max(a, b) == c
|   |  |  |  |
3   1  3  |  2
          false
spock's failure reporting is awesome it shows all values in an assertion as well as the outcome of the assertion this can make debugging tests obsolete

Failure Reporting

class UnrollingTest extends Specification {
    @Unroll
    def "maximum of #left and #right 
         is #maximum" (int left, int right, int maximum) {

        expect:
        Math.max(left, right) == maximum

        where:
        left | right | maximum
        1 | 3 | 2
    }
}
This test shows unrolling, a very cool feature where the variables can be added to the name of the test, and their values will be shown in the error

Failure Reporting

- maximum of 1 and 3 is 2   FAILED

 Condition not satisfied:

 Math.max(left, right) == maximum
      |   |     |      |  |
      3   1     3      |  2
                     false
this can be seen here, the values of left, right and maximum are shown in the test-outcome

Exceptions

class StackSpec extends Specification {
    def "throws on pop when the stack is empty" () {
        given:
        stack = new Stack()

        when:
        stack.pop()

        then:
        thrown EmptyStackException
    }
}
Exceptions, of course, can be handled as well and as could be expected there is also notThrown

Shared Resources

@Shared resource = new ExpensiveResource()
Another cool thing are shared resources, which shares the annotated resource between test methods

Data Tables

class DataDriven extends Specification {
    def "maximum of two numbers"() {
        expect:
        Math.max(a, b) == c

        where:
        a | b || c
        3 | 5 || 5
        7 | 0 || 7
        0 | 0 || 0
    }
}
data tables are one of the great features of spock, in the where block you can specify variable's values and each of the values is evaluated in a seperate iteration

Data Tables

class DataDriven extends Specification {
    def "maximum of two numbers"() {
        expect:
        Math.max(a, b) == c

        where:
        a << [3, 7, 0]
        b << [5, 0, 0]
        c << [5, 7, 0]
    }
}
it can also be written like this

Data Tables

class DataDriven extends Specification {
    def "maximum of two numbers"() {
        expect:
        Math.max(a, b) == c

        where:
        a = 3
        b = Math.random() * 100
        c = a > b ? a : b
    }
}
or in general, just executing code you can also combine the different approaches

Data Tables

class DataDriven extends Specification {
    def "transform numbers" () {
        given:
        NumTransformer trans = new NumTransformer()

        expect:
        trans.transform(arr) == res

        where:
        arr       || res
        null      || null
        []        || [] 
        [1]       || [2]
        [1, 2, 3] || [2, 3, 4]
    }
}
a great real-life use-case are functions taking an array, you usually want the 0-case, the 1-case and the many-case as well as null

Interaction Based Testing

class InteractionTest extends Specification {
    def "" () {
        given:
        ProductService productService = new ProductService()
        Cache cache = Mock()
        productService.setCache(cache)

        when:
        productService.getProduct('123')

        then:
        1 * cache.get('products', '123')
    }
}
Interaction-based testing focuses on an object's behaviour instead of state, it deals with their interactions using mocks, stubs and spies the example shows some productservice which goes to a cache the amount is checked in a very nice way

Cardinality

5 * cache.get('products', '123')
0 * cache.get('products', '123')

(1..5) * cache.get('products', '123')
(1.._) * cache.get('products', '123')
(_..5) * cache.get('products', '123')

_ * cache.get('products', '123')
there are different ways to check how often a method was invoked 5 times zero times 1 to 5 times 1 to x times x to 5 times any number of times, including zero

Constraints

1 * _.get()
1 * cache./get.*s/("123") // getProducts, getArticles
1 * cache._('products', '123') // any method on cache

1 * cache.get('products', '123') // normal
1 * cache.get('products', '!123') // not '123'
1 * cache.get('products', _ as String) // any string
1 * cache.get('products', {it.size() > 2}) // any string longer than 2

1 * cache.get('products', _) // any code
1 * cache.get(*_) // any list of arguments
mocks can also be validated using wildcards for example, this shows that SOME mock's get method was called go through examples

Order

when:
productService.getArticle('123')

then:
1 * cache.get('products', '123')

then:
1 * cache.get('images', '123')
to check the order of invocations just use multiple then-blocks

Stubbing

cache.get(_) >> new Product('123')
cache.get('123') >> new Product('123')
cache.get('789') >> new Product('789')

cache.get(_) >> [new Product('789'), new Product('123')] // multiple

// compute return value
cache.get(_) >> { args -> args[0] == '123' ? new Product('123') : null}

cache.get(_) >> { throw new NoCacheActiveException() }
// closures can be chained as well
to create stubs, methods which return fake values, spock provides really nice, concise syntax Of course, mocks and stubbing can be used together

Spies

class InteractionTest extends Specification {
    def "calls real methods" () {
        given:
        cache = Spy(Cache)

        when:
        productService.getProduct('123') // calls the real cache inside

        then:
        1 * cache.get('products', '123')
    }
}
a spy is based on a real method on a real object, it basically just calls the real method, but records interactions spies can also be stubbed, and in a closure they can call the real method and be extended, this is how partial mocks can be created

Groovy Specials

  • Mock Constructors
  • Mock Statics
  • All-Instance-Mocking
  • For Java -> Powermockup until now, everything works for both groovy and java For Groovy, spock can do about anything, where java needs powermock, which rewrites bytecode under the hood

Extensions

  • @Ignore / @IgnoreRest / @IgnoreIf()
  • @Requires()
  • @Stepwise
  • @AutoCleanup
  • @Timeout()
  • @Subject / @Issue / @Title / @Narrative
  • Spock-Genesisthere are cool annotations in spock multiple ways to ignore a test, or the rest, as well as conditional ignorance of a test requires-clauses, for integration etc., Timeouts and Stepwise, which executes cases in the defined order Also can be extended

Integrations

  • Spring Framework
  • Tapestry
  • Guice
  • Grails
  • Eclipse
  • Intellij

Wrap-Up

spock is great - all in one specs - mocks - spies - data tables - groovy - documentation features extremely concise syntax

Thank You

So, this was a short overview, I hope I could interest you in trying out spock this is my twitter and a link where i will put up my slides and some resources. That's all i got, thanks.
0