Friday, July 25, 2008

TotT: Testing Against Interfaces

To quell a lingering feeling of inadequacy, you took the time to build your own planetary death ray, a veritable rite of passage in the engineering profession. Congratulations. And you were feeling pretty proud until the following weekend, when you purchased the limited-edition Star Wars trilogy with Ewok commentary, and upon watching the Death Star destroy Alderaan, you realized that you had made a bad decision: Your planetary death ray has a blue laser, but green lasers look so much cooler. But it's not a simple matter of going down to Radio Shack to purchase a green laser that you can swap into your existing death ray. You're going to have to build another planetary death ray from the ground-up to have a green laser, which is fine by you because owning two death rays instead of one will only make the neighbors more jealous.

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.

Remember to download this episode of Testing on the Toilet and post it in your office.

6 comments:

  1. I really like this blog. You're doing great job. Keep going.

    ReplyDelete
  2. Wow!
    I went to upgrade my system to use one more laser: cyan

    ReplyDelete
  3. My missile defence system wasn't interface testable and now its all gone to hell! Curse you Google Test Blog!

    ReplyDelete
  4. Great 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.

    ReplyDelete
  5. This 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.

    In 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.

    ReplyDelete
  6. 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!

    ReplyDelete

The comments you read and contribute here belong only to the person who posted them. We reserve the right to remove off-topic comments.