Java Contract Testing, a Pragmatic Approach With JUnit4

I recently read the thought provoking article, Simplifying Contract Testing. It does a good job of discussing how Java interfaces act as contracts, and highlighting that method signatures go only so far towards defining the contract, pointing out that the comments/documentation end up describing key contractual behaviors. With key aspects of the contract appearing only in the text, writing useful contract tests is a valuable goal. The article also offered a JUnit solution, but that’s where it fell apart for me. The solution was thorough, but too complex for my tastes, with custom annotations, gratuitous dependency injection, and as many as three extra support classes to test an interface/implementation pair.

Inspired by the topic, I decided to return to the stated problem and look for a simpler, more pragmatic solution. In the end, I came up with a decent approach, using just stock JUnit4. So, lets start at the beginning.

Defining “The Contract”

So what is an interface’s contract? Lets use Iterator as an example. Stripped down, the code defines the method signature contract as:

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

That much is enforced by Java when you implement it. But that isn’t the full contract. Looking at the documentation there are other specified behaviors making up the contract, for example:

  • next should throw NoSuchElementException if hasNext would return false.

Enforcing the Contract With Tests

Since the contract is more then the interface signatures, and can’t effectively be enforced by the compiler, you’ll want to write tests to enforce it, right? These tests will cover the behavior of the signatures that are only described in the documentation. The question becomes, how do you do that efficiently? If your project has a bunch of classes that implement some interface you obviously want to avoid adding code to test the interface’s contract to each implementation’s tests.

A Pragmatic Approach With JUnit4

If you are using JUnit4, here is an outline of a pragmatic approach to contract testing:

  1. Make sure JUnit is only running *Test.java files. With maven’s surefire you do this via <include>**/*Test.java</include>
  2. Create an abstract *Contract.java for contracts you want to test, give them abstract methods to access resources needed by the tests.
  3. Add @Test methods to the *Contract.java to test the desired behaviors.
  4. Have the *Test.java implementation test classes extend the abstracts appropriately.

The Iterator Example

As an example we’ll create the contract test for the Iterator behavior we noted. Create IteratorContact.java:

public abstract class IteratorContract<T> {

    protected abstract Iterator<T> getNonEmptyIterator();

    @Test(expected=NoSuchElementException.class)
    public void nextThrowsExceptionWhenIteratorIsAtEnd() 
                           throws Exception {
        Iterator<T> anIterator = getNonEmptyIterator();
        assert(anIterator != null);
        assert(anIterator.hasNext() != null);
        while(anIterator.hasNext()) {
            anIterator.next();
        }
        anIterator.next();
    }
}

Since the abstract class is not a *Test.java file, JUnit will not try to run its tests. Now create a contract test for any concrete implementations. Take for example an EnumerationIterator which creates an Iterator adapter for an Enumeration. The implementation test would be EnumerationIteratorTest.java and look like:

public class EnumerationIteratorTest 
            extends IteratorContract<String> {
    @Override
    protected Iterator<String> getNonEmptyIterator() {
        Vector<String> strings = 
                 new Vector<String>();
        strings.add("a");
        return new EnumerationIterator(
                     strings.elements());
    }
    // Any other tests you want ...
}

JUnit4 will run that test because its a *Test.java file. The getNonEmptyIterator() will provide an instance to test, and the inherited @Test nextThrowsExceptionWhenIteratorIsAtEnd() method will test the behavior of the contract behavior we desired. For any other Iterator you’ve implemented, just add another *Test.java extending the IteratorContract appropriately. Rinse, lather, repeat.

In Summary

For a single Iterator this isn’t a particularly compelling approach but consider an interface like Comparable, it has only one method, but that one method has at least five key contractual behaviors found only in the documentation:

  • equals returns 0
  • less than returns < 0
  • greater than returns > 0
  • throw runtime exception NullPointerException if the argument is null
  • throw runtime exception ClassCastException if the argument can’t be cast to the current class

If your project has implementations of Comparable sprinkled throughout, which isn’t unrealistic, a contract test implemented as described can get you completely consistent contract testing coverage with minimal code.

Advertisements

One thought on “Java Contract Testing, a Pragmatic Approach With JUnit4

  1. Pingback: JUnit5, Gradle, No Jacoco?! | My name is Ozymandias

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s