TotT: Testing Against Interfaces
Both your planetary death rays should interoperate with a variety of other bed-wettingly awesome technology, so it's natural that they export the same Java API:
public interface PlanetaryDeathRay {
public void aim(double xPosition, double yPosition);
public boolean fire(); /* call this if she says the rebel
base is on Dantooine */
}
public class BlueLaserPlanetaryDeathRay
implements PlanetaryDeathRay { /* implementation here */ }
public class GreenLaserPlanetaryDeathRay
implements PlanetaryDeathRay { /* implementation here */ }
Testing both death rays is important so there are no major malfunctions, like destroying Omicron Persei VIII instead of Omicron Persei VII. You want to run the same tests against both implementations to ensure that they exhibit the same behavior – something you could easily do if you only once defined tests that run against any PlanetaryDeathRay implementation. Start by writing the following abstract class that extends junit.framework.TestCase:
public abstract class PlanetaryDeathRayTestCase
extends TestCase {
protected PlanetaryDeathRay deathRay;
@Override protected void setUp() {
deathRay = createDeathRay();
}
@Override protected void tearDown() {
deathRay = null;
}
protected abstract PlanetaryDeathRay createDeathRay();
/* create the PlanetaryDeathRay to test */
public void testAim() {
/* write implementation-independent tests here against
deathRay.aim() */
}
public void testFire() {
/* write implementation-independent tests here against
deathRay.fire() */
}
}
Note that the setUp method gets the particular PlanetaryDeathRay implementation to test from the abstract createDeathRay method. A subclass needs to implement only this method to create a complete test: the testAim and testFire methods it inherits will be part of the test when it runs:
public class BlueLaserPlanetaryDeathRayTest
extends PlanetaryDeathRayTestCase {
protected PlanetaryDeathRay createDeathRay() {
return new BlueLaserPlanetaryDeathRay();
}
}
You can easily add new tests to this class to test functionality specific to BlueLaserPlanetaryDeathRay.
I really like this blog. You're doing great job. Keep going.
ReplyDeleteWow!
ReplyDeleteI went to upgrade my system to use one more laser: cyan
My missile defence system wasn't interface testable and now its all gone to hell! Curse you Google Test Blog!
ReplyDeleteGreat tip its worked really well for us, originally read about it in Expert One-on-One J2EE Design and Development by Rod Johnson...this book has a great section on unit tests.
ReplyDeleteThis is a cute post, but I respectfully disagree on the usefulness of abstract TestCase classes with test methods in the base class. My project went down this route recently. The problem we experienced was with finding failures. Our tools (IDE) would report a failing test, but it was often a failure in the base class test method and not the subclass test method that we were working on. So we'd experience test failures and not know which class was failing... the base class or the subclass? Without the failure being a "smoking gun" as to the problem, we quickly scaled back on this approach in favor of a composition based strategy.
ReplyDeleteIn summary: if you do this then I recommend NOT putting any more test methods in the subclass and instead defining a new test case for any subclass-specific tests. Otherwise failing tests will be a frustrating exercise in debugging.
Really nice! This way the abstract test case acts as a Template Method and createDeathRay() creates the specific implementation of the interface on every subclass. Other classes which adhere to this contract can be added and tested easily! Keep going!
ReplyDeleteSorry to raise the question/challenge, but with the existence of parametrized testing strategy(@Parameter annotation), is this abstract template test base really necessary since BlueLaserPlanetaryDeathRayTest and Green LaserPlanetaryDeathRayTest are in parallel relationship?
ReplyDelete