It's bugged me for a while about how best to go about unit testing classes that implement multiple interfaces where those interfaces are implemented by multiple classes or where a class extends another class and implements one or more interfaces.
What I want to avoid is in each test class implementing the the same set of tests for the behavior specified by the interface. That's not very DRY.
If your classes only implement a single interface then you can have a base class with the tests for the interface and then separate test classes derived from that for each class implementing the interface. The problem arises when your classes implement multiple interfaces - Java doesn't support multiple inheritance for classes so you can't have a base test case for each interface.
One answer with JUnit 4 is to use the parameterized test runner - I haven't thought about JUnit 3, if you're not using JUnit 4 because you're still using a JVM that doesn't support annotations JSE 6 is out now, catch up!
You need to have a test case for your interface and configure this to run with the parameterized runner. You also have a static factory method that provides the various implementations of the interface to be used in each test.
As an example, say the container I'm writing (I'm not really - I know there is Apache Geronimo, JBoss, Hivemind etc - this is just an example) supports services that it can start and stop, so I have an interface Service and the various services that I have, e.g. caching, lookup, logging etc, implement this interface.
-
/**
-
* Represents a service that the container can run.
-
*
-
*/
-
public interface Service {
-
-
/**
-
* Starts the service, may only be called if the service is not started.
-
*
-
* @throws IllegalStateException
-
* if called when the service is already started.
-
*/
-
public void start();
-
-
/**
-
* Indicates whether the service is started
-
*
-
* @return true if the service is started, false otherwise
-
*/
-
public boolean isStarted();
-
}
I have a test case for Service that tests that the various implementations of Service adhere to the general contract. I might have the following three tests:
-
public class ServiceTest {
-
-
private Service service;
-
-
@Test
-
public void shouldNotIndicatedStartedForNewObject() {
-
assertFalse(service.isStarted());
-
}
-
-
@Test
-
public void shouldStartIfNotAlreadyStarted() {
-
service.start();
-
assertTrue(service.isStarted());
-
}
-
-
public void shouldThrowExceptionIfStartedCalledOnAStartedService() {
-
service.start();
-
assertTrue(service.isStarted());
-
service.start();
-
}
-
}
If I have a caching and lookup service and I want to test that both adhere to the contract of the Service interface then I have to annotate the ServiceTest to indicate that it should run with the parameterized runner and provide an annotated factory method that returns a collection of arguments to run the tests with.
So the full testcase is:
-
@RunWith(Parameterized.class)
-
public class ServiceTest {
-
-
private Service service;
-
-
public ServiceTest(final Service service) {
-
this.service = service;
-
}
-
-
@Parameters
-
}
-
-
@Test
-
public void shouldNotIndicatedStartedForNewObject() {
-
assertThat(service, is(not(started())));
-
}
-
-
@Test
-
public void shouldStartIfNotAlreadyStarted() {
-
service.start();
-
assertThat(service, is(started()));
-
}
-
-
public void shouldThrowExceptionIfStartedCalledOnAStartedService() {
-
service.start();
-
assertThat(service, is(started()));
-
service.start();
-
}
-
}
The test classes for CachingService and LookupService now only need deal with testing the specifics of caching or lookups rather than testing adherence to the service specification.
I have yet to use this in production code on a large scale so we'll have to see how it works out, but it seems far better than having to replicate test code amongst several test cases.