SpringUnit Tutorial

Class Under Test 1: CompositeDate

Previous Next
The first class under test, CompositeDate is a simple class that represents a date as a tuple consisting of a year, month and day of the month. Of course, it isn't intended for any production use, but it has some interesting properties for testing, because, as we know, there are many boundary conditions and constraints. CompositeDate imposes preconditions on its clients, namely, that months are integers in the range from 1 to 12, inclusive, and days are integers from 1 to 31. There are many combinations of year, month and day that are invalid, but that satisfy these preconditions. CompositeDate checks for these cases and throws an InvalidDateException whenever these are detected. The task of testing a class like this quickly turns to the identification of boundary and special cases, and then proving that these are all handled correctly. CompositeDate includes three types of state-changing methods that require testing: the constructor, field setters, and field incrementers and decrementers. In the following examples, we will write tests of the setDay method. For these tests, we will start with a valid CompositeDate object, and then call setDay, passing in values that pass validation successfully or that throw exceptions. Here is an excerpt.
package org.springunit.examples;

public class CompositeDate implements Serializable {

    /**
     * Create CompositeDate having day, month and year.
     * @Pre("1 <= day && day <= 31")
     * @Pre("1 <= month && month <= 12")
     * @throws InvalidDateException if day, month and year do
     * not specify a valid date
     */
    public CompositeDate(int year, int month, int day) throws InvalidDateException {
        assert 1 <= day && day <= 31 : "1 <= day && day <= 31";
        assert 1 <= month && month <= 12 : "1 <= month && month <= 12";
        this.day = day;
        this.month = month;
        this.year = year;
        validateDate(year, month, day);
    }
	
    /**
     * @param day The day to set.
     * @Pre("1 <= day && day <= 31")
     * @throws InvalidDateException if day, month and year do
     * not specify a valid date
     */
    public void setDay(int day) throws InvalidDateException {
        assert 1 <= day && day <= 31 : "1 <= day && day <= 31";
        validateDate(this.year, this.month, day);
        this.day = day;
    }
    
    // various other methods not shown
	
    public static boolean isValidDate(int year, int month, int day) {
        return isValidLeapDay(year, month, day) || isValidMonthAndDay(month, day);
    }
	
    public static boolean isValidLeapDay(int year, int month, int day) {
        return (day != 29 || month != 2) || ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0));
    }

    public static boolean isValidMonthAndDay(int month, int day) {
        assert 1 <= month && month <= 12 : "1 <= month && month <= 12";
        assert 1 <= day && day <= 31 : "1 <= day && day <= 31";
        return !((day > 29 && month == 2) || (day > 30 && (month == 4 || month == 6 || month == 9 || month == 11)));
    }

    protected static void validateDate(int year, int month, int day) throws InvalidDateException {
        validateLeapDay(year, month, day);
        validateMonthAndDay(month, day);
    }
	
    protected static void validateLeapDay(int year, int month, int day) throws InvalidDateException {
        if (!isValidLeapDay(year, month, day)) {
            throw new InvalidDateException("February 29 is not valid for the year " + year);
        }
    }

    protected static void validateMonthAndDay(int month, int day) throws InvalidDateException {
        if (!isValidMonthAndDay(month, day)) {
            throw new InvalidDateException(day + " is not valid for the month " + month);
        }
    }

    private int day;
    private int month;
    private int year;
	
}
Previous Next