Wednesday, July 19, 2006

Test Driving Interface Usage

One of the neat things (or drawbacks) with Generics in Java is that the left hand side of an assignment can't always be treated separately to the right hand side because of type inferencing.

A simple example is Collections.emptySet(). By itself it will produce an empty set of Objects. If the left hand side defines a Set of Integers (or you use a cast) then that's what it will return.

Unexpectedly, this makes it possible to enforce an interface on the left hand side of an assignment without using something like Checkstyle or other ways of enforcing coding conventions.

The answer seems to be to create a generic object factory which wraps using reflection to create an object. The simplest API would be something like:
<T> T create(Class<T> concreteClass, Object... values).

To create an empty ArrayList and an ArrayList with a default capacity of 255 you would write:
List list = creator.create(ArrayList.class);
List list = creator.create(ArrayList.class, 255);

This allows you to create expectations such that a call to create must return an interface. Using EasyMock test code is something like (from memory):
List list = createMock(List.class);
creator.create(ArrayList.class);
creatorControl.andReturn(list);

The following creates an exception as it causes the wrong type to be created (an ArrayList not a List):
ArrayList list = creator.create(ArrayList.class)

When the API is this simple it can become quite difficult to work out which constructor should be called. The simplest, at least initially, is to fail when there are multiple constructors of the same length or to create a more complicated API where you specify the constructor types. You could also have more complicated behaviour like calling the most specific constructor, where there are multiple matches for the given objects, could create a more sophisticated version. Starting with the constructor arguments and matching against the objects seems like a quicker implementation than trying to match the objects against the constructors.

No comments: