Friday, March 23, 2007

Writing JUnit 4 parameterized tests with varargs data

One of the many excellent new features of JUnit 4 is parameterized tests. These are data-driven tests where a test case runs repeatedly against a collection of test data, as if each run were its own test case.

A standard example:

@RunWith(Parameterized.class)
public class ExampleDataDrivenTest {
    public static final class Data {
        private final String name;

        public Data(final String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    private final List<Data> data;

    public ExampleDataDrivenTest(final Data datum1, final Data datum2) {
        System.out.println("ExampleDataDrivenTest");

        this.data = asList(datum1, datum2);
    }

    @Test
    public void dumpData() {
        for (final Data datum : data)
            System.out.println("datum = " + datum);
    }

    @Parameters
    public static Collection<Data[]> data() {
        final Data[][] data = new Data[][]{
                {new Data("apple"), new Data("core")}};

        return asList(data);
    }
}

This produces:

ExampleDataDrivenTest
datum = apple
datum = core

This works great for a fixed number of test data, but I want to test a variable number of data. The change is straight-forward once you recall that the varargs Java language feature is implemented as an array:

@RunWith(Parameterized.class)
public class ExampleDataDrivenTest {
    public static final class Data {
        private final String name;

        public Data(final String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    private final List<Data> data;

    public ExampleDataDrivenTest(final Data... data) {
        System.out.println("ExampleDataDrivenTest");

        this.data = asList(data);
    }

    @Test
    public void dumpData() {
        for (final Data datum : data)
            System.out.println("datum = " + datum);
    }

    @Parameters
    public static Collection<Data[][]> data() {
        final Data[][][] data = new Data[][][]{
                {{new Data("apple"), new Data("core")}}};

        return asList(data);
    }
}

Reflect on the constructor and you see:

public ExampleDataDrivenTest(ExampleDataDrivenTest$Data[])

This makes the change more obvious.

I can now write my annotated parameters as:

    @Parameters
    public static Collection<Data[][]> data() {
        final Data[][][] data = new Data[][][]{{{ /* no data */ }},
                {{new Data("apple"), new Data("core")}},
                {{new Data("baltimore")}}};

        return asList(data);
    }

To produce:

ExampleDataDrivenTest
ExampleDataDrivenTest
datum = apple
datum = core
ExampleDataDrivenTest
datum = baltimore

9 comments:

Anonymous said...

Wow, this is seriously convoluted, and it violates the DRY principle in many ways. Compare to TestNG's version:

@DataProvider
public Object[][] parameters() {
return new Object[][] {
new Object[] { new Data("apple"), new Data("core") },
}
}

@Test(dataProvider = "parameters")
public void f(Data d1, Data d2) {
// ...
}

Why does JUnit make this simple concept so complicated?!?

Brian Oxley said...

Anonymous,

It is not so complicated. Your TestNG example is not using varargs. :)

Anonymous said...

varargs is already supported, nothing impressive about it, it's all reflection based.

Are you seriously saying the JUnit example is as straightforward as TestNG's?

If you are, then we definitely have different standards for readability.

Brian Oxley said...

I was not planning on a religious war of unit testing frameworks. I prefer JUnit; there is nothing wrong with TestNG.

I consider TestNG over-engineered, but that is a matter of taste.

Anonymous said...

Very interesting!

anca luca said...

Thanks a lot, I found this while looking for an example of parameterized tests, better than the one in junit4 javadoc.

Pretty good examples!

Dissertation Help said...

it's good to see this information in your post, i was looking the same but there was not any proper resource, thanx now i have the link which i was looking for my research.

Get a Blog said...

Thanks for so good and informative blog about JUint.

Anonymous said...

I have to agree with poster writing the first comment. If you have independent tests by running the same test method with different data TestNG will run the test method several times and report its result separately for each run. In case of JUnit concept you have to implement that your own. Furthermore you will not get any detailed information about which test failed with given test data on the test result. You will only see that test method XYZ has failed running with data ABC. If following tests were successful is unknown.