JUnit Parameterized Tests
As previously mentioned, I went in search of a way to remove a lot of the duplication I had within the tests. In this particular case, I had a situation where I had a large number of input conditions, but it should exercise the same code (a round of poker) and verify the output (who won).
One option I considered was to go down the BDD route. I've used JBehave and so knew I could specify each scenario simply in a text file. I think this may be a little overkill for what I'm trying to do, so I look within JUnit itself first.
JUnit does support something called parameterized tests. This effectively allows you to have one test method which can receive a set of different input arguments. Sounds just what I need, so I look at the implementation.
As a quick spike I try this out using a simple Calculator example. The calculator is a trivial class:
class Calculator {
public int add(int a, int b) { return a + b; }
}
To test this, I want to verify the add is doing what is says on the tin:
class CalculatorTest {
@Test
public void can_add() {
Assert.assertEquals(expectedResult, new Calculator().add(a, b);
}
}
Which won't compile of course, as the values used in the assert haven't been defined. Let's add the fields and a constructor:
private final int a, b, expectedResult;
public CalculatorTest(int a, int b, int expectedResult) {
this.a = a;
this.b = b;
this.expectedResult = expectedResult;
}
Now it will compile at least. Trying to run it using JUnit will fail, as it will expect one public no-arg constructor. We need to say this will be a parameterized test by adding a RunWith annotation to the class:
@RunWith(Parameterized.class)
public class CalculatorTest {
Getting close. Now JUnit is telling us it expects a public static parameters method. A little cryptic perhaps. Put simply, what we need is a method which will return the three necessary int arguments to our constructor. The way JUnit needs this data is as a Collection<Object[]>. Each element of the collection being one 'test' and the Object[] element matching the constructor arguments. Finally this method should have the @Parameters annotation. Putting this together, we get the following:
@Parameterized.Parameters
public static Collection<Object[]> rounds() {
return Arrays.asList(new Object[][] {
{ 1, 2, 3 }, // 1 + 2 = 3
{ 2, 3, 5 }, // 2 + 3 = 5
{ 3, 5, 8 } // 3 + 5 = 8
});
}
And this now passes. Not too tricky.
For the poker kata, I decided to create a simple helper class which takes the players, hands, and any folding details, along with the expected output. The test class constructor then simply takes an instance of this class, and the test method asserts the round results are as expected. I'm really pleased with the simplification of the tests this allows which has set me on a mission to create many more test scenarios with little fuss.
For completeness, here is the full listing of the test class:
import java.util.Arrays;
import java.util.Collection;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class CalculatorTest {
@Parameterized.Parameters
public static Collection<Object[]> rounds() {
return Arrays.asList(new Object[][] {
{ 1, 2, 3 }, // 1 + 2 = 3
{ 2, 3, 5 }, // 2 + 3 = 5
{ 3, 5, 9 } // 3 + 5 = 8
});
}
private final int a, b, expectedResult;
public CalculatorTest(int a, int b, int expectedResult) {
this.a = a;
this.b = b;
this.expectedResult = expectedResult;
}
@Test
public void can_add() {
Assert.assertEquals(expectedResult, new Calculator().add(a, b));
}
}
