tl;dr

We show how Spock test improves expressivity and readability of tests over JUnit 4. Download a sample setup based on Spock 1.3 on Groovy 2.5 from github.com/JohannesFKnauf/regex-tdd-parameterized-spock-example and start playing.

What are your testing weapons of choice, then?

In my last blog article about testing regular expressions with JUnit 4 Parameterized tests I stated

Colleague: “Java 8, JUnit 4, Maven.”

Me: “Not exactly my weapons of choice, but well… will do. Let’s get ready to rumble!”

People asked me, what solution I would prefer instead of JUnit 4. To be honest: Pretty much anything. In this post I want to present Spock as a testing toolkit that focuses on expressive language. By the power of Groovy and its Grapes, we also show how to run it as a self-contained script instead of creating a lot of maven boilerplate.

Both aspects – test language and test execution – are separate. In principle, you can also

  • run JUnit tests as a self-contained groovy script and
  • run Spock using your build tool of choice, e.g. maven or gradle.

Running spock as a self-contained groovy script

For the purpose at hand, setting up a baroque maven project with a lot of boilerplate feels like overkill. We only want to test a simple Regex, after all.

Get the Groovy 2.5 distribution and install it. In doubt, use the default way. Alternative mechanisms you might want to look at are

After installing we expect you to have a way of calling groovy like this:

# groovy --version
Groovy Version: 2.5.6 JVM: 1.8.0_191 Vendor: Oracle Corporation OS: Linux

Implementing parameterized Data Driven Testing in Spock

In order to achieve the same goal as JUnit 4 parameterized tests we use a Spock feature called Data Driven Testing. Let’s go straight to the code:

regex-test.groovy

@Grab('org.spockframework:spock-core:1.3-groovy-2.5')
@GrabExclude('org.codehaus.groovy:groovy-nio')
@GrabExclude('org.codehaus.groovy:groovy-macro')
@GrabExclude('org.codehaus.groovy:groovy-sql')
@GrabExclude('org.codehaus.groovy:groovy-xml')

import spock.lang.Unroll

class RegexSpec extends spock.lang.Specification {
  String REGEX = /[-+]?\d+(\.\d+)?([eE][-+]?\d+)?/

  @Unroll
  def 'matching example #example for case "#description" should yield #isMatchExpected'(String description, String example, Boolean isMatchExpected) {
    expect:
    isMatchExpected == (example ==~ REGEX)

    where:
    description                                  | example        || isMatchExpected
    "empty string"                               | ""             || false
    "single non-digit"                           | "a"            || false
    "single digit"                               | "1"            || true
    "integer"                                    | "123"          || true
    "integer, negative sign"                     | "-123"         || true
    "integer, positive sign"                     | "+123"         || true
    "float"                                      | "123.12"       || true
    "float with exponent extension but no value" | "123.12e"      || false
    "float with exponent"                        | "123.12e12"    || true
    "float with uppercase exponent"              | "123.12E12"    || true
    "float with non-integer exponent"            | "123.12e12.12" || false
    "float with exponent, positive sign"         | "123.12e+12"   || true
    "float with exponent, negative sign"         | "123.12e-12"   || true
  }
}

Did you see it? No XML mumbo-jumbo. Almost no syntactic clutter. Just the tests, the whole tests and nothing but the tests. Lovely, isn’t it?

There are some points, that the anguished experienced developer will highly appreciate:

  • Test methods carry free-form string names, noStupidAndHardToReadMixedCaseSentencesWithALotOfFourLetterAcronymsLikeJfgiOrRofl. YouWillLoveIt.
  • Test method names contain named (not numbered) template parameters, which correspond to the method parameters. @Unroll ensures that each case is considered a separate test.
  • Test sections are separated by natural language labels like expect: and where: (the implementation is powered by the Groovy compiler AST tranformation extensions; LISP-like features with Java-like syntax; Groovy!).
  • Test cases are presented in a clearly arranged, easy to read and easy to write ASCII table.
  • The regular expression in / ... / Groovy syntax does not suffer from the backslash plague, but is otherwise translated to a plain java.util.regex.Pattern when used with the tilde operators like ==~.

Running the tests

# groovy regex-test.groovy
JUnit 4 Runner, Tests: 13, Failures: 0, Time: 85

Well, that does not look so different than before. Under the hood, Spock is driven by JUnit 4 Runner. That is a good thing, as it means you can use Spock in your IDE without any adjustments.

Reaping the benefits of superior failure reports

Just to demonstrate one of the biggest advantages of adopting Spock, we will intentionally break the tests:

  // ...
  String REGEX = /bogus_regex/
  // ...
# groovy regex-test.groovy
JUnit 4 Runner, Tests: 13, Failures: 9, Time: 93
...
Test Failure: matching example 123.12e-12 for case "float with exponent, negative sign" should be true(RegexSpec)
Condition not satisfied:

isMatchExpected == (example ==~ REGEX)
|               |   |       |   |
true            |   |       |   bogus_regex
                |   |       false
                |   123.12e-12
                false

	at RegexSpec.matching example #example for case "#description" should be #isMatchExpected(regex-test.groovy:16)

Without any debugging, you can exactly see what went wrong. No wonder they are called “Power assertions”.

One Caveat: Spock’s compatility issues

In the end I want to spend a word on the drawbacks: The combination Spock + Groovy sometimes summons the demons from dependency hell. Did you notice the @GrabExclude boilerplate? This is the remedy to get the script running.

Other advantages of Spock like using Groovy’s dynamic typing for testing Java libraries or even private methods are beyond the scope of this article. But if you like to hear about it, just drop us a line.



Contact us