Parameterized unit tests with JUnit 4

I'm going to share a few non-proprietary posts that I wrote for the benefit of my coworkers. I'll start with a post on JUnit. The JUnit 4 series introduced a range of features that make writing tests both easier and more expressive, but finding documentation for even simple features can be difficult.

Parameterized tests

If you've ever found yourself writing a series of tests which differ only in their inputs and expected results, you've probably realized that the sensible thing to do would be to abstract your tests into a single test that can be run against a varying set of data. JUnit 4 allows you to do this with either theories or parameterized tests; here, I'll discuss the latter.

Structure of a parameterized test class

To mark a test class as a parameterized test, you must first annotate it with @RunWith(Parameterized.class). The class must then provide at least three entities:

  1. A static method that generates and returns test data,
  2. A single constructor that stores the test data, and
  3. A test.

The method that generates test data must be annotated with @Parameters, and it must return a Collection of Arrays. Each array represents the data to be used in a particular test run. The number of elements in each array must correspond to the number of parameters in the class's constructor, because each array element will be passed to the constructor, one at a time as the class is instantiated over and over.

The constructor is simply expected to store each data set in the class's fields, where they can be accessed by the test methods. Note that only a single constructor may be provided. This means that each array provided by the data-generating method must be the same size, and you might have to pad your data sets with nulls if you don't always need a particular value.

Let's put this together. When the test runner is invoked, the data-generating method will be executed, and it will return a Collection of Arrays, where each array is a set of test data. The test runner will then instantiate the class and pass the first set of test data to the constructor. The constructor will store the data in its fields. Then each test method will be executed, and each test method will have access to that first set of test data. After each test method has executed, the object will be instantiated again, this time using the second element in the Collection of Arrays, and so on.

Parameterized test example

A sample test class with comments is below. In this class, each data set consists of a single test input and an expected result, but you can put any data that you need in there.

    1 import java.util.Arrays;
    2 import java.util.Collection;
    3 
    4 import org.junit.Test;
    5 import org.junit.runners.Parameterized;
    6 import org.junit.runners.Parameterized.Parameters;
    7 import org.junit.runner.RunWith;
    8 
    9 import static org.hamcrest.CoreMatchers.\*;
   10 import static org.junit.Assert.\*;
   11 
   12 import com.yoyodyne.something.Whatever;
   13 
   14 @RunWith(Parameterized.class)
   15 public class ParameterizedTestExample
   16 {
   17    // Fields
   18    private String datum;
   19    private String expectedResult;
   20    
   21    /\*
   22     \* Constructor.
   23     \* The JUnit test runner will instantiate this class once for every
   24     \* element in the Collection returned by the method annotated with
   25     \* @Parameters.
   26     \*/
   27    public ParameterizedTestExample(String datum, String expected)
   28    {
   29       this.datum = datum;
   30       this.expected = expected;
   31    }
   32    
   33    /\*
   34     \* Test data generator.
   35     \* This method is called the the JUnit parameterized test runner and
   36     \* returns a Collection of Arrays.  For each Array in the Collection,
   37     \* each array element corresponds to a parameter in the constructor.
   38     \*/
   39    @Parameters
   40    public static Collection<Object[]> generateData()
   41    {
   42       // In this example, the parameter generator returns a List of
   43       // arrays.  Each array has two elements: { datum, expected }.
   44       // These data are hard-coded into the class, but they could be
   45       // generated or loaded in any way you like.
   46       return Arrays.asList(new Object[][]) {
   47          { "AGCCG", "AGTTA" },
   48          { "AGTTA", "GATCA" },
   49          { "GGGAT", "AGCCA" }
   50       }
   51    }
   52    
   53    /\*
   54     \* The test.
   55     \* This test method is run once for each element in the Collection returned
   56     \* by the test data generator -- that is, every time this class is
   57     \* instantiated. Each time this class is instantiated, it will have a
   58     \* different data set, which is available to the test method through the
   59     \* instance's fields.
   60     \*/
   61    @Test
   62    public void testWhatever()
   63    {
   64       Whatever w = new Whatever();
   65       String actualResult = w.doWhatever(this.datum);
   66       assertThat(actualResult, is(this.expectedResult));
   67    }
   68 }
   69 
Comments:

The current issue with these tests is that there is no way to provide names for the single test case. All are named

testWhatEver[0]
testWhatEver[1]
...

Do you have a workaround for that?

Cheers, Jörg

Posted by Jörg Thönnes on March 05, 2010 at 02:31 AM CST #

Post a Comment:
Comments are closed for this entry.
About

A weblog about identity management and testing. See here.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today