In the Parametrized JUnit Tests blog post I explained how to create a parametrized junit test. In this blog post I will discuss the reaction of one of my colleagues.
After reading my blog post about parametrized junit tests a colleague expressed his objections with this way of testing. He was a bit disappointed with one thing in particular. The signature of the public static method that is used to construct the test.
@Parameters public static Collection<Object[]> data()
Is returns a collection of Object arrays. One Object array for each set of parameters. The reason this should be an Object array becomes clear from it's usage.
List<Object[]> data = new ArrayList<Object[]>(); data.add(new Object[] { 2, new Integer[] { 2 } }); return data;
Because the parameters used are of different type, in this case an Integer and an Integer array, they only super-type they have in common is Object.
The objection of my colleague was that is berefts the compiler of the oppertunity to detect type errors at compile time. The compiler would happily compile the following code. But it would blow up at run time.
List<Object[]> data = new ArrayList<Object[]>(); data.add(new Object[] { 2, new Integer[] { 2 } }); data.add(new Object[] { 2, new Double[] { 2.0 } }); return data;
After some discussion with my colleague we came up with the following solution. Instead of using a lot of parameters in the constructor, use only one. By using the builder pattern one retains the type information. Furthermore because now only one parameter is needed, the nearest super-type of all the parameters is the type of the parameter itself.
The code that follows show the same test as in the previous blog post. The change is reflected in the use of the builder pattern. Note that a fluent interface is used to easily construct a builder for the test.
One could still object that the array is now superfluous. They are correct. One way to deal with their objections is to write an custom runner.
@RunWith(Parameterized.class) public class FactorizationTest { private int number; private List<Integer> expectedResult; public FactorizationTest(FactorizationTestBuilder builder) { this.number = builder.getNumber(); this.expectedResult = builder.getExpectedResult(); } @Test public void factorize() { assertEquals(expectedResult, Factorization.factor(number)); } @Parameters public static Collection<FactorizationTestBuilder[]> data() { List<FactorizationTestBuilder[]> data = new ArrayList<FactorizationTestBuilder[]>(); data.add(new FactorizationTestBuilder[] { FactorizationTestBuilder.withNumber(2).expect(2) }); data.add(new FactorizationTestBuilder[] { FactorizationTestBuilder.withNumber(8).expect(2, 2, 2) }); data.add(new FactorizationTestBuilder[] { FactorizationTestBuilder.withNumber(9).expect(3, 3) }); data.add(new FactorizationTestBuilder[] { FactorizationTestBuilder.withNumber(72).expect(2, 2, 2, 3, 3) }); return data; } } class FactorizationTestBuilder { private int number; private List<Integer> expectedResult = new ArrayList<Integer>(); public static FactorizationTestBuilder withNumber(int number) { return new FactorizationTestBuilder(number); } private FactorizationTestBuilder(int number) { this.number = number; } public FactorizationTestBuilder expect(Integer... expectedResult) { this.expectedResult = Arrays.asList(expectedResult); return this; } public int getNumber() { return number; } public List<Integer> getExpectedResult() { return expectedResult; } }
In this blog post I described the objections of a colleague on the previous Parametrized Junit Test blog. I have shown had to meet these objections by using the builder pattern.