Assertion Extensions for JUnit
By Tony Morris2005-05-05
Use Case Scenario
Suppose you've been given the requirement to implement a class that represents a person. The Person class requires three attributes: the person's title, first name, and surname. The title attribute can be one of a finite set of possible values -- MR, MS, and MRS -- so you intend to implement a Title class using the Typesafe Enumeration design pattern. The UML diagram in Figure 1 shows these requirements as they currently stand.
Figure 1. UML diagram of requirements
Listing 1 shows the source code for these requirements:
public class Person {
private Title title;
private String firstName;
private String surName;
}
public class Title {
public static final Title MR = new Title();
public static final Title MS = new Title();
public static final Title MRS = new Title();
// private constructor to prevent outside instantiation
private Title() {
}
}The source code now needs to become robust and fully functional by meeting a more specific set of requirements, such as the following typical ones:
• The Person class overrides the equals(Object) and hashCode() methods according to their contracts so that it can be used effectively in Collection types.
• The Person class implements the java.io.Serializable interface and serializes and deserializes without producing any errors.
• The Person class is not declared final, so that it can be subclassed.
• The Title class is declared final because it doesn't expose any constructors (which implies that it cannot be subclassed). It's good practice to document the acknowledgment of this design decision so that the final modifier can be seen in the source code and generated API documentation.
• The Title class implements the java.io.Serializable interface, serializes and deserializes without producing any errors, and serializes to the same instance (in accordance with the requirements of the Typesafe Enumeration design pattern).
• The Title class has a private default (no argument) constructor to prevent instantiation from outside the class.
You can assert all of these requirements easily by using JUnitX in a JUnit test environment. The source code in Listing 2 is a set of JUnit test cases to assert that all of the requirements are met using JUnitX capabilities:
Listing 2. JUnit test cases using JUnitX assertions
import junit.framework.TestCase;The Person and Title classes in Listing 1 won't pass the test cases in Listing 2, because they don't meet all of the additional requirements. You can now develop the classes to meet the new requirements so that the unit test cases pass, which implies that the requirements have been met. Listing 3 shows an example of an implementation that meets the specified requirements:
import junitx.framework.ObjectFactory;
import junitx.framework.Assert;
import java.io.Serializable;
import java.lang.reflect.Constructor;
public class TestRequirements extends TestCase {
public void testPersonEqualsAndHashCodeContract() {
// Different surnames should be unequal.
ObjectFactory factory = new ObjectFactory() {
public Object createInstanceX() {
return new Person(Title.MR, "Bob", "Brown");
}
public Object createInstanceY() {
return new Person(Title.MR, "Bob", "Smith");
}
}
// Make sure the object factory meets its contract for testing.
// This contract is specified in the API documentation.
Assert.assertObjectFactoryContract(factory);
// Assert equals(Object) contract.
Assert.assertEqualsContract(factory);
// Assert hashCode() contract.
Assert.assertHashCodeContract(factory);
}
public void testPersonSerialization() {
// Assert that the Person class directly implements Serializable.
Assert.assertDirectInterfaceOf(Person.class, Serializable.class);
// Assert that the Person instance can be serialized and deserialized without errors.
Assert.assertSerializes(new Person(Title.MR, "Joe", "Blog"));
}
public void testPersonNotFinal() {
// Assert that the Person class is not declared final.
Assert.assertNotFinal(Person.class);
}
public void testTitleFinal() {
// Assert that the Title class is declared final.
Assert.assertFinal(Title.class);
}
public void testTitleSerialization() {
// Assert that the Title class directly implements Serializable.
Assert.assertDirectInterfaceOf(Person.class, Serializable.class);
// Assert that the Title instances can be serialized and deserialized without errors.
Assert.assertSerializes(Title.MR);
Assert.assertSerializes(Title.MS);
Assert.assertSerializes(Title.MRS);
// Assert that serialization results in the same instance.
Assert.assertSerializesSame(Title.MR);
Assert.assertSerializesSame(Title.MS);
Assert.assertSerializesSame(Title.MRS);
}
public void testTitleConstructor() {
// Assert that the Title class has a default constructor.
Assert.assertClassHasConstructor(Title.class, null);
try {
// Get the default constructor.
Constructor con = Title.class.getDeclaredConstructor(null);
// Assert that the default constructor is declared private.
Assert.assertPrivate(con);
}
catch(NoSuchMethodException nsme) {
// Should never get here, even when test fails.
throw new IllegalStateException();
}
}
}
Listing 3. Revised classes meeting additional requirements
import java.io.Serializable;If you run the JUnit test cases on the classes in Listing 3, the tests pass, so you can conclude that your code meets the given requirements.
public class Person implements Serializable {
private Title title;
private String firstName;
private String surname;
public Person(Title title, String firstName, String surname) {
this.title = title;
this.firstName = firstName;
this.surname = surname;
}
public boolean equals(Object o) {
// Performance optimization only.
if(this == o) {
return true;
}
if(o == null) {
return false;
}
if(!(o instanceof Person)) {
return false;
}
Person p = (Person)o;
return title == p.title && firstName.equals(p.firstName) && surname.equals(p.surname);
}
public int hashCode() {
final int oddPrime = 461;
int result = 73;
result = result * oddPrime + title.hashCode();
result = result * oddPrime + firstName.hashCode();
result = result * oddPrime + surname.hashCode();
return result;
}
}
import java.io.Serializable;
public final class Title implements Serializable {
public static final Title MR = new Title();
public static final Title MS = new Title();
public static final Title MRS = new Title();
private static int nextIndex = 0;
private final int index = nextIndex++;
private static final Title[] VALUES = new Title[]{MR, MS, MRS};
private Title() {
}
// Ensure that the same instance is returned when deserialized.
Object readResolve() {
return VALUES[index];
}
}
Tutorial Pages:
» AlphaWorks Package Allows you to Perform Unit Testing with Complex Assertions More Easily
» Getting Started with JUnitX
» Use Case Scenario
» Conclusion
» Resources
First published by IBM DeveloperWorks
