SpringUnit Tutorial

Example 6

Previous Next
The first step we take is to gather the common code into an abstract class, RangeTutorialExample6TestAbstract. Note that even though this is an abstract class, the SpringUnit framework requires a SpringUnitContext object whose name follows the same convention we have already seen. Notice that this class is now generic: it has a generic parameter of type T, and T must conform to Comparable<T>. Inside the tests we will create objects of type Range, which is parameterized by T with exactly this constraint.
package org.springunit.examples.tutorial;

import org.springunit.examples.Range;
import org.springunit.framework.SpringUnitContext;
import org.springunit.framework.SpringUnitTest;

public abstract class RangeTutorialExample6TestAbstract<T extends Comparable<T>> extends SpringUnitTest {

    public void testIsWithinBelowLower() throws Exception {
        runIsWithin();
    }
    
    public void testIsWithinAtLower() throws Exception {
        runIsWithin();
    }
    
    public void testIsWithinBetween() throws Exception {
        runIsWithin();
    }
    
    public void testIsWithinAtUpper() throws Exception {
        runIsWithin();
    }
    
    public void testIsWithinAboveUpper() throws Exception {
        runIsWithin();
    }
    
    protected void runIsWithin() throws Exception {
        Range<T> subject = getObject("subject");
        T item = getObject("item");
        boolean expected = getObject("expected");
        boolean actual = subject.isWithin(item);
        assertEquals(expected, actual);
    }
    
    public SpringUnitContext getRangeTutorialExample6TestAbstract() {
        return this.rangeTutorialExample6TestAbstract;
    }

    public void setRangeTutorialExample6TestAbstract(SpringUnitContext rangeTutorialExample6TestAbstract) {
        this.rangeTutorialExample6TestAbstract = rangeTutorialExample6TestAbstract;
    }
    
    private SpringUnitContext rangeTutorialExample6TestAbstract;

}
We will need separate configuration files for each of the types to be tested, so we will need corresponding classes to reference them. First, here is the code for RangeTutorialExample6CompositeDateTimeTest.
package org.springunit.examples.tutorial;

import org.springunit.examples.CompositeDateTime;
import org.springunit.framework.SpringUnitContext;

public class RangeTutorialExample6CompositeDateTimeTest extends RangeTutorialExample6TestAbstract<CompositeDateTime> {

    public SpringUnitContext getRangeTutorialExample6CompositeDateTimeTest() {
        return rangeTutorialExample6CompositeDateTimeTest;
    }

    public void setRangeTutorialExample6CompositeDateTimeTest(
            SpringUnitContext rangeTutorialExample6CompositeDateTimeTest) {
        this.rangeTutorialExample6CompositeDateTimeTest = rangeTutorialExample6CompositeDateTimeTest;
    }

    private SpringUnitContext rangeTutorialExample6CompositeDateTimeTest;

}
Here is the code for RangeTutorialExample6CalendarTest.
package org.springunit.examples.tutorial;

import java.util.Calendar;

import org.springunit.examples.RangeSpringUnitTestAbstract;
import org.springunit.framework.SpringUnitContext;

public class RangeTutorialExample6CalendarTest extends RangeSpringUnitTestAbstract<Calendar> {

    public SpringUnitContext getRangeTutorialExample6CalendarTest() {
        return rangeTutorialExample6CalendarTest;
    }

    public void setRangeTutorialExample6CalendarTest(
            SpringUnitContext rangeTutorialExample6CalendarTest) {
        this.rangeTutorialExample6CalendarTest = rangeTutorialExample6CalendarTest;
    }

    private SpringUnitContext rangeTutorialExample6CalendarTest;

}
Next, we'll examine the data configuration files, starting first with an excerpt from RangeTutorialExample6CalendarTest.xml.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>

    <bean id="rangeTutorialExample6CalendarTest" class="org.springunit.framework.SpringUnitContext">
        <property name="data">
            <map>
            
                <!-- First entry omitted -->
                
                <entry key="testIsWithinBelowLower">
                    <map>
                        <entry key="item">
                            <bean class="java.util.GregorianCalendar">
                                <constructor-arg><value type="int">2006</value></constructor-arg>
                                <constructor-arg><value type="int">5</value></constructor-arg>
                                <constructor-arg><value type="int">1</value></constructor-arg>
                                <constructor-arg><value type="int">8</value></constructor-arg>
                                <constructor-arg><value type="int">10</value></constructor-arg>
                                <constructor-arg><value type="int">19</value></constructor-arg>
                            </bean>
                        </entry>
                    </map>
                </entry>
                
                <entry key="testIsWithinAtLower">
                    <map>
                        <entry key="item">
                            <bean class="java.util.GregorianCalendar">
                                <constructor-arg><value type="int">2006</value></constructor-arg>
                                <constructor-arg><value type="int">5</value></constructor-arg>
                                <constructor-arg><value type="int">1</value></constructor-arg>
                                <constructor-arg><value type="int">8</value></constructor-arg>
                                <constructor-arg><value type="int">10</value></constructor-arg>
                                <constructor-arg><value type="int">20</value></constructor-arg>
                            </bean>
                        </entry>
                    </map>
                </entry>
                
                <!-- More test data follows -->
                
            </map>
        </property>
    </bean>
    
</beans>
In this excerpt, it is plain how the values of the items are set. However, the test algorithm is expecting three values for each test: item, subject, and expected. Where are these declared? Before we answer that, we should look back at the bean configuration files for Example 4 and Example 5 and look for redundancies. Within each file, the declaration of subject is the same for every test. Therefore, instead of repeatedly declaring the subject Range object for each test, we can instead declare it once in the scope of the entire test case. Here is an excerpt from the same file, but now with the previously omitted first entry shown.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>

    <bean id="rangeTutorialExample6CalendarTest" class="org.springunit.framework.SpringUnitContext">
        <property name="data">
            <map>
            
                <entry key="subject">
                    <bean class="org.springunit.examples.Range">
                        <constructor-arg>
                            <bean class="java.util.GregorianCalendar">
                                <constructor-arg><value type="int">2006</value></constructor-arg>
                                <constructor-arg><value type="int">5</value></constructor-arg>
                                <constructor-arg><value type="int">31</value></constructor-arg>
                                <constructor-arg><value type="int">20</value></constructor-arg>
                                <constructor-arg><value type="int">30</value></constructor-arg>
                                <constructor-arg><value type="int">40</value></constructor-arg>
                            </bean>
                        </constructor-arg>
                        <constructor-arg>
                            <bean class="java.util.GregorianCalendar">
                                <constructor-arg><value type="int">2006</value></constructor-arg>
                                <constructor-arg><value type="int">5</value></constructor-arg>
                                <constructor-arg><value type="int">1</value></constructor-arg>
                                <constructor-arg><value type="int">8</value></constructor-arg>
                                <constructor-arg><value type="int">10</value></constructor-arg>
                                <constructor-arg><value type="int">20</value></constructor-arg>
                            </bean>
                        </constructor-arg>
                    </bean>
                </entry>

                <entry key="testIsWithinBelowLower">
                    <map>
                        <entry key="item">
                            <bean class="java.util.GregorianCalendar">
                                <constructor-arg><value type="int">2006</value></constructor-arg>
                                <constructor-arg><value type="int">5</value></constructor-arg>
                                <constructor-arg><value type="int">1</value></constructor-arg>
                                <constructor-arg><value type="int">8</value></constructor-arg>
                                <constructor-arg><value type="int">10</value></constructor-arg>
                                <constructor-arg><value type="int">19</value></constructor-arg>
                            </bean>
                        </entry>
                    </map>
                </entry>
                
                <entry key="testIsWithinAtLower">
                    <map>
                        <entry key="item">
                            <bean class="java.util.GregorianCalendar">
                                <constructor-arg><value type="int">2006</value></constructor-arg>
                                <constructor-arg><value type="int">5</value></constructor-arg>
                                <constructor-arg><value type="int">1</value></constructor-arg>
                                <constructor-arg><value type="int">8</value></constructor-arg>
                                <constructor-arg><value type="int">10</value></constructor-arg>
                                <constructor-arg><value type="int">20</value></constructor-arg>
                            </bean>
                        </entry>
                    </map>
                </entry>
                
                <!-- More test data follows -->
                
            </map>
        </property>
    </bean>
    
</beans>
We can now note an important feature of the SpringUnit framework: there is a prescribed search order for locating items held in the maps inside the SpringUnitContext. Starting with the context associated with the test class at the bottom of the class hierarchy, the framework searches for an object by name in the scope of the current test. If it is found, it is returned. If it is not found, then it searches in the enclosing map of the same class. If found here, then the object is returned. (In this example, you can see that subject is a peer of the tests.) If the name of the object is not found for this class, then the framework recursively searches the ancestor classes until a match is found, or there are no more ancestors to search. Applying this approach works here because for every test, the subject is initialized to the same value, and because the method being tested (isWithin) does not alter the state of the subject, the subject's state is preserved across each test invocation.

To this point, we have seen how items and subjects are configured, but what about the expected values? Given what you have just learned about SpringUnit's search algorithm, you may have guessed that these are located in the configuration file associated with the ancestor class. Here is the file for RangeTutorialExample6TestAbstract.xml.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>

    <bean id="rangeTutorialExample6TestAbstract" class="org.springunit.framework.SpringUnitContext">
        <property name="data">
            <map>
                <entry key="testIsWithinBelowLower">
                    <map>
                        <entry key="expected">
                            <value type="boolean">false</value>
                        </entry>
                    </map>
                </entry>
                <entry key="testIsWithinAtLower">
                    <map>
                        <entry key="expected">
                            <value type="boolean">true</value>
                        </entry>
                    </map>
                </entry>
                <entry key="testIsWithinBetween">
                    <map>
                        <entry key="expected">
                            <value type="boolean">true</value>
                        </entry>
                    </map>
                </entry>
                <entry key="testIsWithinAtUpper">
                    <map>
                        <entry key="expected">
                            <value type="boolean">true</value>
                        </entry>
                    </map>
                </entry>
                <entry key="testIsWithinAboveUpper">
                    <map>
                        <entry key="expected">
                            <value type="boolean">false</value>
                        </entry>
                    </map>
                </entry>
            </map>
        </property>
    </bean>
    
</beans>

Unlike the test data, which varies with each data type (and therefore must be partitioned into two separate tests and files) the expected results are invariant across all data types. Therefore, it is appropriate to refactor the configuration of these values into the file associated with the abstract class.

Previous Next