Class Under Test 1: CompositeDate
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;
}