Tuesday 11 September 2012

Testing Java using the Spock Framework

The testing framework Spock is a powerful DSL built on Groovy which enables easily writable and extremely readable tests which lends itself well to software development processes like TDD and BDD.

As an example of how much clarity Spock and Groovy bring to testing I'll compare it with the tests for the ForkJoinPool example class InterpolationService.

Every Spock test (or specification) extends spock.lang.Specification. This abstract class uses Spock's JUnit runner, org.spockframework.runtime.Sputnik, and contains useful methods for writing tests eg creating mock objects

There are four main aspects to a specification: Fields, Fixtures, Features and Helpers. There is no point going into depth on each as there are well documented on the Spock Basics wiki but we'll certainly concentrate on Features with respect to the comparison with the JUnit InterpolationService tests.

Features are instance methods which must contain at least one block where a block is one of six methods: setup, when, then, expect, cleanup and where. The blocks setup and cleanup are as you might expect but others are described in more detail below.

First though and the first few lines of the InterpolationServiceTest are shown below:

package com.city81.interpolation.service

import spock.lang.*

class InterpolateServiceTest extends Specification {

    @Shared def interpolateService = new InterpolateService()

The class under test ie InterpolationService is marked as being @Shared (ie all features use the same instance).  This annotation is ideal for expensive resources like a DB connection.

Next the first feature which tests the interpolation of two numbers with an even number of steps:

def "interpolate two numbers with even no. of steps"() {
            
            expect:
            interpolateService.interpolate(a, b, c) == d
            
            where:
            a   | b    | c | d
            5.0 | 25.0 | 4 | [5.0, 10.0, 15.0, 20.0, 25.0] 
            }


The equivalent JUnit assert test method would be:

@Test
 public void testInterpolateTwoValues() {
     List<Double> interpolatedList = 
         interpolateService.interpolate(5.0, 25.0, 4);

    assertEquals(5, interpolatedList.size());
    assertEquals(5.0, interpolatedList.get(0), 0);
    assertEquals(10.0, interpolatedList.get(1), 0);
    assertEquals(15.0, interpolatedList.get(2), 0);
    assertEquals(20.0, interpolatedList.get(3), 0);
    assertEquals(25.0, interpolatedList.get(4), 0);
}


The clarity in what you are testing is easy to see and it is so easy with Spock just to add another where line to test another scenario:

def "interpolate two numbers with even no. of steps"() {

            expect:
            interpolateService.interpolate(a, b, c) == d

            where:
            a   | b    | c | d
            5.0 | 25.0 | 4 | [5.0, 10.0, 15.0, 20.0, 25.0] 
            2.0 | 14.0 | 6 | [2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0] 
            }

It is also worth noting at this point that Groovy uses BigDecimal for floating number numbers but if you want to denote doubles then numbers can be appended with a d eg 5.0d, -15.0d

Whilst the above demonstrates the expect and where blocks, the below shows how to test for exceptions using when and then blocks:

def "interpolate two numbers where first arg is null"() {
       
            when:
            interpolateService.interpolate(null, 25.0, 4)

            then:
            thrown(IllegalArgumentException)
            }

An alternative approach to using thrown(IllegalArgumentException) could be to bind a variable to access the exception:

then:
            IllegalArgumentException e = thrown()
            e.cause == InterpolateService.VALUE_ARG_NOT_NULL_EXCP_TEXT

You can also use the when and then blocks to order interactions although not if the first when throws an exception.

There is much more on Spock on it's Google code pages and the above only describes a few basics but the next post will look at Mocking and also examine it's use with Spring by testing code from a previous post. Might even get the Groovy code formatting working by then too!

No comments:

Post a Comment

Note: only a member of this blog may post a comment.