Design by contract: testing implementations of interfaces

Here’s a nice way of associating contracts with interfaces and testing the implementations conform.

A sample interface:

``

interface CheeseMaker {
int getCheeseCount();
void addCheese(Cheese cheese);
}

With the contracts:

You can create an abstract unit test for this interface that tests these contracts. The only thing it doesn’t do is provide an implementation – instead it has an abstract factory method.

``

public abstract class CheeseMakerTest extends TestCase {

// abstract factory method
protected abstract CheeseMaker createCheeseMaker();

public void testZeroCheesesOnCreation() {
CheeseMaker cheeseMaker = createCheeseMaker();
assertEquals(0, cheeseMaker.getCheeseCount());
}

public void testAddingACheeseIncrementsCount() {
CheeseMaker cheeseMaker = createCheeseMaker();
cheeseMaker.addCheese(new Cheese("Cheddar"));
cheeseMaker.addCheese(new Cheese("Wensleydale"));
assertEquals(2, cheeseMaker.getCheeseCount());
}

public void testDuplicateCheesesDoNotIncrementCount() {
CheeseMaker cheeseMaker = createCheeseMaker();
cheeseMaker.addCheese(new Cheese("Cheddar"));
cheeseMaker.addCheese(new Cheese("Cheddar"));
assertEquals(1, cheeseMaker.getCheeseCount());
}

public void testNullCheeseCausesIllegalArgumentException() {
CheeseMaker cheeseMaker = createCheeseMaker();
try {
cheeseMaker.addCheese(null);
fail("expected exception");
} catch (IllegalArgumentException e) {} // good
}

}

Now, every time you create an implementation of CheeseMaker, the test should extend CheeseMakerTest and you inherit the contract tests for free.

For example:

``

public class BigCheeseMaker implements CheeseMaker {
// ... ommited for sanity
}

public class BigCheeseMakerTest extends CheeseMakerTest {

// factory method implementation
protected CheeseMaker createCheeseMaker() {
return new BigCheeseMaker();
}

// ... any additional tests go here

}

It’s important to note that this tests the contract but does not enforce them. It’s very flexible and there are very few contracts (if any at all) that couldn’t be expressed in a unit-test.

This helps defensive development with the added safety of unit-tests.

As a bonus, you can use TestDox to generate documentation for interfaces (much more useful than implementations), like so:

h4. CheeseMaker