From JUnit to SpringUnit Tests

Separating Data from Algorithm

Previous Next
Viewed more critically, Removing Duplicate Code didn't really eliminate all redundancy. Notwithstanding the important observation that the data values vary, the establishment of inputs and expected values follows the same pattern from one test to the next. Therefore, if we could extract the assignment of these values into something common, the amount of code in each test could be correspondingly reduced. The first step down this path is the biggest leap: we will remove the data values from each test and store them in a separate data struture. For each test, there exists a set of heterogenous data items (inputs and expected values). This suggests a map indexed by the names of the tests, where the value of each entry is itself a map, having a name describing the purpose of the item that serves as the key to the value. The skeleton of the new TestCase appears here, showing the declaration and initialization of the data structre.
package org.springunit.examples.constructor.junit.v3;

public class CompositeDateConstructorTest extends TestCase {
	
    private Map<String, Map<String, Object>> data;

    protected void setUp() throws Exception {
        this.data = new HashMap<String, Map<String, Object>>();
        // Population of data values follows here
    }
	
}
The data field, a map of maps, will hold all of the data values for all of the tests. It is instantiated and populated with values in the setUp method. Recall that the setUp method is inherited from the JUnit framework. It is called before executing the body of every test method in the class. As we shall see, for any given test class, this data structure could become arbitrarily large. This has ramifications for performance that we will return to later. A fuller excerpt of the setUp method follows.
    protected void setUp() throws Exception {
        this.data = new HashMap<String, Map<String, Object>>();
		
        Map<String, Object> testData = new HashMap<String, Object>();
        testData.put("day", 1);
        testData.put("month", 1);
        testData.put("year", 2006);
        testData.put("expectedDay", 1);
        testData.put("expectedMonth", 1);
        testData.put("expectedYear", 2006);
        this.data.put("testJan01", testData);
        
        // more valid test data here
		
        testData = new HashMap<String, Object>();
        testData.put("day", 29);
        testData.put("month", 2);
        testData.put("year", 2004);
        testData.put("expectedDay", 29);
        testData.put("expectedMonth", 2);
        testData.put("expectedYear", 2004);
        this.data.put("testFeb29Leap2004", testData);
		
        testData = new HashMap<String, Object>();
        testData.put("day", 31);
        testData.put("month", 4);
        testData.put("year", 2006);
        this.data.put("testApr31", testData);
		
        // more invalid test data here
		
        testData = new HashMap<String, Object>();
        testData.put("day", 29);
        testData.put("month", 2);
        testData.put("year", 2003);
        this.data.put("testFeb29NoLeap", testData);
    }
Because the test values must now be retrieved from this data structure, the test methods become considerably more verbose. Here is an excerpt.
    public void testJan01() throws Exception {
        Integer day = (Integer)this.data.get("testJan01").get("day");
        Integer month = (Integer)this.data.get("testJan01").get("month");
        Integer year = (Integer)this.data.get("testJan01").get("year");
        Integer expectedDay = (Integer)this.data.get("testJan01").get("expectedDay");
        Integer expectedMonth = (Integer)this.data.get("testJan01").get("expectedMonth");
        Integer expectedYear = (Integer)this.data.get("testJan01").get("expectedYear");
        runValid(year, month, day, expectedYear, expectedMonth, expectedDay);
    }
	
    // More valid tests here

    public void testFeb29Leap2004() throws Exception {
        Integer day = (Integer)this.data.get("testFeb29Leap2004").get("day");
        Integer month = (Integer)this.data.get("testFeb29Leap2004").get("month");
        Integer year = (Integer)this.data.get("testFeb29Leap2004").get("year");
        Integer expectedDay = (Integer)this.data.get("testFeb29Leap2004").get("expectedDay");
        Integer expectedMonth = (Integer)this.data.get("testFeb29Leap2004").get("expectedMonth");
        Integer expectedYear = (Integer)this.data.get("testFeb29Leap2004").get("expectedYear");
        runValid(year, month, day, expectedYear, expectedMonth, expectedDay);
    }

    public void testApr31() throws Exception {
        Integer day = (Integer)this.data.get("testApr31").get("day");
        Integer month = (Integer)this.data.get("testApr31").get("month");
        Integer year = (Integer)this.data.get("testApr31").get("year");
        runInvalid(year, month, day);
    }

    // More invalid tests here

    public void testFeb29NoLeap() throws Exception {
        Integer day = (Integer)this.data.get("testFeb29NoLeap").get("day");
        Integer month = (Integer)this.data.get("testFeb29NoLeap").get("month");
        Integer year = (Integer)this.data.get("testFeb29NoLeap").get("year");
        runInvalid(year, month, day);
    }
We can reduce the clutter in two steps. First, we'll Eliminate the Test Name and then we'll Add Syntactic Sugar
Previous Next