«« Superstar DJ ... Here We Go! XAML »»
blog header image
Unit Testing Multiple Interface Implementations

Sometimes you want to test several implementations of an Interface that give exactly the same results but with different performance characteristics, for example. Since they are supposed to work the same way you want to use the same tests on each of the implementations to confirm that.

You could make copies of the tests for each class and manually make sure they are the same but it's a maintenance nightmare. We ran into this problem with the AudioMan project and here was our first solution, using the Java List collection as an example. Vector and ArrayList implement the List Interface.

public class ListTest extends TestCase
{
  List list = null;

  public void setList(List implementation)
  {
    this.list = implementation;
  }

  public void testExample1()
  {
    // test code here
  }

  public void testExample2()
  {
    // test code here
  }
}

public class VectorTest extends TestCase
{
  ListTest listTest = new ListTest();

  public VectorTest()
  {
    this.listTest.setList(new Vector());
  }

  public void testExample1()
  {
    this.listTest.testExample1();
  }

  public void testExample2()
  {
    this.listTest.testExample2();
  }
}

All of the test implementations are in ListTest. To test the Vector implementation of the List Interface, make functions of the same names in VectorTest that call the corresponding test in ListTest.

This is better than copying the tests because the implementations are only in one class. But as you add new tests to ListTest you have to remember to add the respective calls to the new test to VectorTest and ArrayListTest too. It's better than keeping copies of whole tests but there's still a maintenance problem with this solution. So I was thinking about it this morning and I came up with something else:

public abstract class ListTest extends TestCase
{
  List list = null;

  protected void setUp() throws Exception
  {
    this.list = this.getList();
  }

  protected abstract List getList();

  public void testExample1()
  {
    // test code here
  }

  public void testExample2()
  {
    // test code here
  }
}

public class VectorTest extends ListTest
{
  protected List getList()
  {
    return new Vector();
  }
}

public class ArrayListTest extends ListTest
{
  protected List getList()
  {
    return new ArrayList();
  }
}

With this solution, the test code is still only in one spot: the interface test class ListTest. Then each of the implementing class' test classes subclasses ListTest instead of TestCase and implements the abstract getList() method returning its own implementation of the List Interface, in this case Vector and ArrayList.

All of the test methods are are inherited from ListTest, so VectorTest and ArrayListTest can be executed with JUnit. ListTest can't be run as a test because it is an abstract class.

So there we have it. No more maintenance problems and it's dead easy to add a new implementation's test class.

Update Sunday 12:50pm Andrew suggested I make the ListTest class abstract. I like the idea so I've changed my post. We don't run into that filtering problem with Ant because we put our tests into classifications and only run tests ending with TestA with Ant, but for other projects using the test class names to filter is a good idea.

Posted at November 08, 2003 at 09:28 PM EST
Last updated November 08, 2003 at 09:28 PM EST
Comments

I would suggest going one step further and making ListTest into AbstractListTest (for filtering purposes with something like Ant) and making the getList() list method abstract.

But otherwise, good to go on the idear - it fits like a glove.

» Posted by: aforward at November 9, 2003 07:17 AM

Yes, that's a great idea. Thanks buddy.

» Posted by: Ryan at November 9, 2003 12:20 PM

I had tried to implement the pattern for testing interfaces with JUnit that I found on:
http://www.placebosoft.com/abstract-test.html

But I wasn't able to with AudioMan because the classes that we have implementing the interface are singletons -> have a static method. Since this pattern doesn't work for that, that's where it fails.

But I really like your idea... much nicer than what we have. ;-)

» Posted by: Jim at November 9, 2003 12:24 PM

After reading over the document that I posted a reference to, it looks just what what you proposed. I guess that I just didn't get it when I was doing reading it the first time.

damn. :-(

» Posted by: Jim at November 9, 2003 02:00 PM

Yes indeed. :)

» Posted by: Ryan at November 9, 2003 03:46 PM
Google
 
Search scope: Web ryanlowe.ca