Tuesday, July 6, 2010

Groovy & Spock: Adding Numbers Together

Lately, I've been playing around with embedding Mule in Tomcat. To get going, I wanted to start with creating an extermely simple service, and along the way I decided to try out the Spock specification framework to test the logic I'd be using in my service. I liked what I found so much, I thought I'd take a little detour and write about my first use of the framework, comparing it to more a common test class, and save the Tomcat posting for another day.

The simple service that will eventually be developed will take a list of numbers and add them up. Here is the class that will ultimately do the math:

class Adder {
Integer add(List<Integer> numbers) {
println "adding: $numbers"
numbers.inject(0) { sum, item -> sum + item }
}
}

The add method takes a list of numbers and returns their sum. I'm using Integer as the return type and List on the parameter declaration instead of the def keyword to aid in the generation of the service WSDL down the line. While the println won't make into the final version of the class, it might help us out demonstrating what is being sent to the method during testing.

With Spock we'll be writing a specification that describes the behavior of the Adder class:

  • Given a list of numbers, they should be added together to produce a sum.
  • Given a list containg no numbers, zero should be returned.

The framework's documentation does a great job explaining how to write a spec, so I will not to try and reproduce much of it here. The spec is pretty much self-explanatory, each of the two requirements manifests itself in a method:

import spock.lang.*

class AdderSpec extends Specification {

def adder = new Adder()

def "numbers should be added together to produce a sum"() {
expect:
sum == adder.add(numbers)
where:
sum | numbers
4 | [2, 2]
6 | [1, 2, 3]
10 | [1, 2, 3, 4]
1 | [0, 1]
0 | [0, 0]
}

def "summing no numbers at all should return zero"() {
expect:
sum == adder.add(numbers)
where:
sum | numbers
0 | []
0 | null
}
}

The where clauses resemble a table, with each entry describing the input to the add() method on the right, and the expected result on the left. The first entry in the first method would have the values for sum and numbers substituted into the expect clause so that it would read:

4 == adder.add([2, 2])

The output from the test run helps illustrate how things are added during a run of the spec:

Running org.prystasj.services.adder.AdderSpec
adding: [2, 2]
adding: [1, 2, 3]
adding: [0, 1]
adding: []
adding: null
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.091 sec

If the value for numbers instead read [2, 3], we would get the following failure when we run the test as 2+3 does not equal 4:

Condition not satisfied:

sum == adder.add(numbers)
| | | | |
4 | | 5 [2, 3]
| org.prystasj.services.adder.Adder@969c29
false

While I could of used a helper method to remove the redunduncy between the two expect clauses, I liked this output such much, I decided to keep it, as using a method here would modify the resulting failure description. For example:

check(sum, numbers)
| | |
false 4 [2, 3]

Compare the above spec class to a more traditional unit test and see what you think:

import org.junit.Before
import org.junit.Test
import static org.junit.Assert.assertEquals

class AdderTest {

def underTest

@Before
void setUp() {
underTest = new Adder()
}

def verifySumsFor(data) {
data.each { sum, numbers ->
assertEquals "$sum <= $numbers", sum, underTest.add(numbers)
}
}

@Test
void test_numbers_added_together_produce_a_sum() {
def data = [
4 : [2, 2],
6 : [1, 2, 3],
10 : [1, 2, 3, 4],
1 : [0, 1],
0 : [0, 0],
]
verifySumsFor(data)
}

@Test
void test_summing_no_numbers_at_all_should_return_zero() {
def data = [
0 : [],
0 : null,
]
verifySumsFor(data)
}
}

Which version do you like better? Thanks for reading.

2 comments:

  1. Nice blog post. You can get the same condition output in helper methods like so:

    void check(sum, numbers) {
    assert sum == adder.add(numbers)
    }

    However, this mostly makes sense when you keep repeating a whole bunch of conditions.

    ReplyDelete
  2. Hello John

    I'm working with Mule and liking very much what I'm reading about Spock. Did you manage to have something equivalent to Mule's FunctionalTrestCase with Spock?

    Thanks for your time!

    ReplyDelete