<iframe src="//www.googletagmanager.com/ns.html?id=GTM-MXN9JJ" height="0" width="0" style="display:none;visibility:hidden">

The Smaato Blog

Building better software tests with Spock

Home » Blog » Building better software tests with Spock
Posted by Gerd Rohleder on October 14, 2016
spock-blog-Hero_logo.jpg

Development is one of the key activities that build, enable and ultimately power the overall mobile environment.  Today we are sharing some key insight into an important aspect of development – testing framework – from one of our most experienced developers here at Smaato.

Developers know that choosing the right testing framework for the job can be really complicated. The jUnit Framework is an excellent Java testing tool, but for specific tests that require sample data, better tools - like the Groovy-based Spock - are availableThis post describes how to use Spock and how it handles test data. It also explores how Spock tests can be executed with jUnit tests and maven.

How jUnit runs parameterized tests

To have a test that works with a set of test data, you need to define a data method which returns all samples in a Collection. Each sample is used to instantiate a new test class. The constructor needs suitable parameters for the test samples. Then all test methods are executed, as you can see in the code samples below:

package spocky;

@RunWith(Parameterized.class)

public class SayHelloTest {

private final SayHello hello = new SayHello();

private final String toWho;

private final String expectedResult;

 

@Parameters(name = "Say Hello to {0}")

public static Collection<Object[]> data() {

return Arrays.asList(new Object[][] {{"Dr. Who", "hello Dr. Who"},

   {"world”, "hello world" }});

}

 

public SayHello(final String toWho, final String expectedResult){

this.toWho = toWho;

this.expectedResult = expectedResult;

}

@Test

public void sayHello(){

final String result = hello.speak(toWho);

assertThat(result, is(expectedResult));

}

@Test

public void saySomethingElse() throws Exception {

System.out.println("somethingElse");

}

}

Result

Spock_Blog_JUnit_Image.png

Here we can see some major problems with jUnit parameterized tests:

  • Constructor and attributes are required (boiler code)
  • A separate runner is required 
  • A test is executed for every data sample (wasteful for tests without sample data)
  • Only one set of sample data is allowed per test class

Certain test frameworks like JUnitParams offer different approaches that make tests more readable. But I was unable to find a pure Java-based approach that fit the bill, so I looked around for a solution and found Spock.

How to use Spock (a short introduction)

Spock is a Groovy-based test framework. It uses the jUnit TestRunner, which means you can code in your favorite IDE and build tools to write and execute Spock specifications.

Project setup

These maven dependencies are required at setup:

<dependency>

<groupId>org.spockframework</groupId>

<artifactId>spock-core</artifactId>

<version>1.0-groovy-2.4</version>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.codehaus.groovy</groupId>

<artifactId>groovy-all</artifactId>

<version>2.4.7</version>

<scope>test</scope>

</dependency>

We also need a /src/test/groovy directory in our project, which will contain all Groovy-related files.

"Hello world"

With Spock, you write specifications that describe the features your system has (or should have).

Each specification must extend from the spock.lang.Specification and should be named with “Specification” as a postfix. This detail will be important later in the build process.

import spock.lang.Specification

class HelloWorldSpecification extends Specification {

def "say hello to the world"(){

}

}

Feature methods are named with String literals - this way, you can write more readable names. Each feature has the same phases as any unit test:

  1. Setup (optional)
  2. Execution
  3. Verification
  4. Clean up (optional)

Here’s a short “hello world” example of how to implement these phases with Spock:

def "say hello to the world"(){

given:

def helloWorld = new HelloWorld()

when:

def result = helloWorld.speak()

then:

result == "hello world"

}

In this example, we use three blocks to define our feature method:

  1. Given – an alias for setup of this feature method (Spock has more fixture methods like setupSpec which are executed once before the first feature method).
  2. When – a description of the stimulus to our software under test; the execution phase.
  3. Then - a description of the expected response; the verification phase.

More blocks can be found in Spock documentation, which I recommend to you for review.

Groovy and jUnit tests together with maven

When writing tests in jUnit and specifications in Spock, we want to execute them both to test our system. In maven, we use the gmavenplus and surefire plugins to do that. The gmavenplus plugin compiles all Groovy files it finds in src/test/groovy:

<plugin>

<groupId>org.codehaus.gmavenplus</groupId>

<artifactId>gmavenplus-plugin</artifactId>

<version>1.5</version>

<executions>

<execution>

<goals>

<goal>addTestSources</goal>

<goal>testCompile</goal>

</goals>

</execution>

</executions>

</plugin>

The surefire plugin is configured to execute specifications together with all other tests:

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-surefire-plugin</artifactId>

<version>2.18.1</version>

<configuration>

<includes>

<include>**/*Test.java</include>

<include>**/*Specification.java</include>

</includes>

</configuration>

</plugin>

To set our Java version to 1.8, we write the following plugin configuration:

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<version>3.3</version>

<configuration>

<source>1.8</source>

<target>1.8</target>

</configuration>

</plugin>

How Spock handles parameterized specification methods

Spock uses data tables to define test data for a feature method. The nice thing here is that you can have multiple methods with their own test data, unlike jUnit tests which have to share test data. To define a data table, we use the Where block and it must be the last block of a method.

def "say hello"(){

given:

def helloWorld = new HelloWorld()

when:

def result = helloWorld.speak toWho

then:

result == expectedResult

where:

toWho | expectedResult

"Dr. Who" | "helloDr. Who"

"world" | "hello world"

}

In this example, I introduced a minor bug in the test data to demonstrate how Spock shows failing tests:  

Spock_Blog_Spocky_Image.png

Above, we see a failing test and the failing assertion stacktrace, which raises a number of interesting points:

  • The printout of Spock assertion errors are a lot better than jUnit or hamcrest.
  • We do not see the result of the second data sample.

To fix this issue, we use the Spock.lang.Unroll annotation. This enables the visibility of each iteration as a separate feature. If we use this together with test data in the method name, then we receive a clear overview of what happened.

@Unroll

def "say hello #toWho"(){... 

Spock_Blog_Finished_Image.png

Summary

It's clear that jUnit is an excellent test framework for all kind of Java tests, but there are some test scenarios where it's not a good fit. For tests with test data, the jUnit Parameterized solution has some disadvantages that can be solved by using Spock, a Groovy-based test framework. You can use multiple specification methods and each of them is directly connected to their own test data. It's possible to use jUnit and Spock tests together, so you should consider this option as part of your overall testing strategy.

Do you know of better ways to write tests with test data? If so, please leave a comment on this blog and we can discuss further.

Written by Gerd Rohleder

Gerd is Smaato's Senior Java Developer in our platform's Core Technology team. He is a test-driven development practitioner and has extensive experience in behavior-driven development.

Want the latest in mobile advertising monetization strategies & ideas in your inbox? Subscribe today!