Monday, May 17, 2004

The Abstract Test Pattern (ATP)

I have received a few comments on my blog entry on Composite Unit Testing (CUT) arguying that this was the Abstract Test Pattern . Here's a snapshot of the definition from the definition form http://c2.com/cgi/:

A Testing Pattern describing a way to reuse test cases for multiple implementations of an Interface.
Problem
How to write a Test Suite against an Interface (or Abstract Class) that can be used to test all implementations of the interface.

Solution

  • Write an AbstractTest  for every Interface and Abstract Class). The AbstractTest should have an abstract FactoryMethod that creates an object with the type of the Interface.
  • Write a ConcreteTest for every implementation of the Interface. The ConcreteTest? should be a descendant of the AbstractTest and override the FactoryMethod to construct an instance of the implementation class.

Functional Compliance

Eric George's article gives a more detailled description of the pattern and describes it as functional compliance. It is easy enough for the compiler to tell whether a class is syntactically compliant with an interface. It applies a check to see if all required methods have been implemented with the correct signatures (syntaxic compliance), but the compiler cannot check functional compliance of a class with its interface. Here's the formal definition given by Eric George:

Functional Compliance is a module's compliance with some documented or published functional specification. The specification can be purely documentational, or it can be partially enforced through Interfaces or Abstract Classes. Interfaces and Abstract Classes along with their associated documentation represent a contract between the implementation code and the client (or user) code. It is this contract that needs to be fully tested. The Liskov Substitution Principle (LSP) tells us that all modules that honor a contract (usually by implementing an interface), should behave the same from the perspective of the client code. A module's functional compliance is really the degree to which it obey's the LSP.

So what about Composite Unit Testing ?

The remarks from the readers were right. Composite Unit Testing is

  • an enhanced form of the Abstract Test Pattern,
  • is a tool to test functional compliance

There is, however, a major difference between ATP and CUT: separation of the test code and the factory methods. In AUT, you create a ConcreteTest that inherits AbstractTest and implements a factory method, so the code that generates the tested entity is "hard-coded" into concrete test. In CUT, the framework takes care of retreiving and feeding you AbstractTest using user-specified factories (you can easily have multiple factories):

// AUT
// abstract method
public abstract class AbstractEnumerableTest
{
    public IEnumerable Create();
    public void GetEnumeratorTest()
    {
        IEnumerable en = this.Create();
        ...
    }
}

// concrete implementation
[TestFixture]
public class ArrayListEnumerableTest
{
    public override IEnumerable Create()
    { return new ArrayList();}
}

The same test as above, using CUT:

// the fixture
public class EnumerableFixture
{
    public void GetEnumeratorTest(IEnumerable en)
    {
        ...
    }
}

// the factories
public class ArrayListFactory
{
    public ArrayList Emtpy
    { get{ return new ArrayList();}}
}

// link the fixture with the factories
[CompositeFixture(typeof(EnumerableFixture), typeof(IEnumerable))]
[ProviderFactory(typeof(ArraListFactory),typeof(IEnumerable))]
public class EnumerableTest
{}
posted on Monday, May 17, 2004 9:42:00 PM UTC  #    Comments [3]
Monday, June 06, 2005 6:03:40 PM UTC
NUnitAddin
Monday, June 06, 2005 6:03:41 PM UTC
I don't understand two things:
<br>
<br>1. What duplication does this pattern avoid?
<br>2. Why would I want multiple factories?
<br>
<br>It is clear to me that you are solving a problem I've never had before. If you can describe the problem or requirement that leads you to go from AbstractTestCase to CompositeUnitTest, that might clarify the point.
<br>
J. B. Rainsberger
Monday, June 06, 2005 6:03:42 PM UTC
Let's consider the testing of ArrayList and Hashtable as in the example.
<br>
<br>1)
<br>Suppose we want to test enumeration. If you use the Abstract Test Pattern, you create a AbstractEnumerableTest like this:
<br>
<br>public abstrac
<br>t class AbstractEnumerationTest
<br>{
<br> protected IEnumerable enumerable;
<br>
<br> [Test]
<br> public void GetEnumerator()
<br> {
<br> IEnumerator en = enumerable.GetEnumerat();
<br> ...
<br> }
<br>}
<br>
<br>The abstract pattern is then implemented for the two colleciont:
<br>
<br>public ArrayEnumerableTest : AbstractEnumerableTest
<br>{
<br> [SetUp]
<br> public void SetUp()
<br> {
<br> this.enumerable = new ArrayList();
<br> }
<br>}
<br>public HashtableEnumerableTest : AbstractEnumerableTest
<br>{
<br> [SetUp]
<br> public void SetUp()
<br> {
<br> this.enumerable = new Hashtable();
<br> }
<br>}
<br>
<br>The SetUp methods are potential code duplication because if you create another test fixture for the collection, you will need to rewrite the SetUp.
<br>
<br>Why not integrating the new fixture into the abstract one? Take IList for example, ArrayList implements IList, Hashtable does not so this would be irrelevant.
<br>
<br>2)
<br>I suppose you mean that one factory provides different instances of the same class. There are number of reasons to that:
<br>
<br>- you should create the class using each available constructor and make sure it works as expected,
<br>- in the example of collection (and ArrayList more precisely), you want to hit the parts of the code the rescale the vector. This part of the code will not be hit if the number of elements is &lt; 16. So by providing a ArrayList with Count &gt; 16, you know you have hit that part of the code.
Jonathan de Halleux
Comments are closed.