After much pondering and some dead-ends I have begun using the following pattern:
In the following:
- [INTERFACE] refers to the interface being tested.
- [CLASS] refers to the implementation of the interface being tested.
Interface tests are built so that developers may test that implementations meet the contract
set out in the interface and accompanying documentation.
The major items under test use an instance of an [INTERFACE]ProducerInterface to create the instance of the object being tested. An implementation of [INTERFACE]ProducerInterface must track all the instances created during the test and close all of them when requested. There is an Abstract[INTERFACE]Producer that handles most of that functionality but requires a createNewINTERFACE implementation.
TESTS
Interface tests are noted as Abstract[INTERFACE]Test. Tests generally extend the Abstract[INTERFACE]ProducerUser class. This class handles cleaning up all the graphs at the end of the tests and provides a hook for implementers to plug in their [INTERFACE]ProducerInterface implementation.
In general to implement a test requires a few lines of code as is noted in the example below
where the new Foo graph implementation is being tested.
public class FooGraphTest extends AbstractGraphTest {
// the graph producer to use while running
GraphProducerInterface graphProducer = new FooGraphTest.GraphProducer();
@Override
protected GraphProducerInterface getGraphProducer() {
return graphProducer;
}
// the implementation of the graph producer.
public static class GraphProducer extends AbstractGraphProducer {
@Override
protected Graph createNewGraph() {
return new FooGraph();
}
}
}
SUITES
Test suites are named as Abstract[INTERFACE]Suite. Suites contain several tests that excersize all of the tests for components of the object under test. For example if the Foo.getBar() returned an instance of the Bar interface the Foo suite includes tests for the Foo iteself as well as running the Bar tests the Bar. Running the suites is a bit more complicated then running the tests.
Suites are created using the JUnit 4 @RunWith(Suite.class) and @Suite.SuiteClasses({ })
annotations. This has several effects that the developer should know about:
- The suite class does not get instantiated during the run.
- The test class names must be known at coding time (not run time) as they are listed in the annotation.
- Configuration of the tests has to occur during the static initialization phase of class loading.
To meet these requirements the Abstract[INTERFACE]Suite has a static variable that holds the instance of the [INTERFACE]ProducerInterface and a number of local static implementations of the Abstract tests that implement the "get[INTERFACE]Producer()" method by returning the static instance. The names of the local tests are then used in the @Suite.SuiteClasses annotation. This makes creating an instance of the Abstract[INTERFACE]Suite for an [INTERFACE] implementation fairly simple as is noted below.
public class FooGraphSuite extends AbstractGraphSuite {
@BeforeClass
public static void beforeClass() {
setGraphProducer(new GraphProducer());
}
public static class GraphProducer extends AbstractGraphProducer {
@Override
protected Graph createNewGraph() {
return new FooGraph();
}
}
}
Note that the beforeClass() method is annotated with @BeforeClass. the @BeforeClass causes it to be run once before any of the test methods in the class. This will set the static
instance of the graph producer before the suite is run so that it is provided to the enclosed tests.
FUTURE
I expect that further simplification and removal duplicate code can be achieved through the use of java generics, but I have not gotten to that point yet.