Channels ▼
RSS

Testing

Simplifying Contract Testing


Assume that A and B have similar AT and BT classes associated with them via the @Contract annotation.

When JUnit runs the CImplContractTest test, it uses the ContractSuite class. That class discovers all the interfaces implemented by CImpl and then locates their associated tests. It uses the implementation of interface C specified by the ContractImpl annotation.

Introduction of the Producer

While the preceding discussion talks about injecting the class under test, the contract testing system actually uses an IProducer interface to define an object that can create new instances of the class under test and provides a mechanism to clean up after the test runs. The @Contract.Inject annotation is used on a method that returns the IProducer for the class under test. The Interface is defined as:

public interface IProducer<T> {
    public T newInstance();
    public void cleanUp();
 } 

Expanding the Example

The @ContractTest annotation was added because several test runners attempt to instantiate and execute generic test classes, while others do not. (It appears that the JUnit classes could use some contract tests of their own.) Use of this annotation prevents these test runners from reporting false failures.

// define the contract test
@Contract(C.class)
public abstract class CT<T extends C> {

    private IProducer<T> producer;

    @Contract.Inject
    protected abstract IProducer<T> 
           setProducer(IProducer<T> producer) {
       this.producer = producer;
    }

    protected final IProducer<T> getProducer() {
          return producer;
    }

    @After
    public final void cleanupCT()
       {
	producer.cleanUp();
    }

    @Test
    public void testCMethod() {}
}

// a test that only runs the contract test
// This is useful for debugging the contract test.
public class CImplTest extends CT<CImpl> {

    public CImplTest() {
        setProducer( new IProducer<CImpl>() {
            @Override
            public CImpl newInstance() {
                return new CImpl();
            }
            @Override
            public void cleanUp() {
             // does nothing
            }
	 });
	}
}

// A contract suite.
// run as a contract suite
@RunWith(ContractSuite.class)
// is the suite for CImpl
@ContractImpl(CImpl.class)
public class CImplContractTest {
    // the producer for the CImpl
    private IProducer<CImpl> producer = 
         IProducer<CImpl>() {
             @Override
             public CImpl newInstance() {
                 return new CImpl();
             }
             @Override
             public void cleanUp() {
                 // does nothing
             }
         };

   // method to inject our test instance into test classes
     @Contract.Inject
   public IProducer<CImpl> getProducer() {
       return producer;
   }
}

When JUnit runs the CImplTest test, only testCMethod() will run. However, when the CImplContractTest class is run, all of the AT, BT and CT tests will be run using the producer from CTImplContractTest to create an instance of CImpl, which is the instance of C, B, or A depending on the test.

Real-Life Usage

As noted previously, Apache Jena has an interface called Graph. At this time, Apache Jena experimental new tests (http://is.gd/v7ecTw) are written using the junit-contracts package to test its framework and to provide packaged tests for integrators developing new instances of the Graph interface.

Graph is an extension point in the Jena project that enables integrators to quickly add additional storage types and strategies. It is expected that integrators will create implementations of this class that function deep inside the Jena application architecture. It is imperative then that the implementors correctly implement the contract of the interface. There are several tests currently provided by the Apache Jena team that the implementer can run. However, there is not one clear test that tests the complete Graph contract. If the experimental tests are accepted by the Jena project, then they will provide a GraphContract test and an abstract GraphProducer that implements IProducer<Graph>. The reason for the abstract GraphProducer is that some graphs must be closed to properly shut them down, but the test interface may create multiple graphs. The AbstractGraphProducer tracks the graphs that are created and properly closes them during cleanup. It also has extension points to handle additional operations after the graph is closed. The AbstracGraphProducer code looks something like this:

public abstract class AbstractGraphProducer<T extends Graph> implements
        IProducer<T> {

    /**
     * List of graphs opened in this test.
     */
    protected List<Graph> graphList = 
         new ArrayList<Graph>();

    /**
     * The method to create a new graph.
     * 
     * @return a newly constructed graph of 
     * type under test.
     */
     abstract protected T createNewGraph();

     @Override
     final public T newInstance() {
         T retval = createNewGraph();
         graphList.add(retval);
         return retval;
     }

     /**
      * Method called after the graph is closed. 
      * This allows the implementer to perform
      * extra cleanup activities, such as deleting 
      * the file associated with a file-based graph.
      * 
      * By default this does nothing.
      * 
      * 
      * @param g The graph that is closed
      */
     protected void afterClose(Graph g) {
     };

     @Override
     final public void cleanUp() {
         for (Graph g : graphList) {
             if (!g.isClosed()) {
                 g.close();
             }
             afterClose(g);
         }
         graphList.clear();
     }
}

Implementers of the Graph interface only need to include the Jena test classes in their project test and code a simple contract running test class.

// A contract suite.

// run as a contract suite
@RunWith(ContractSuite.class)

// is the suite for MyGraphImpl
@ContractImpl(MyGraph.class)
public class MyGraphContractTest {
    // the producer for the MyGraph
       private IProducer<MyGraph> producer = 
        new AbstractGraphProducer<MyGraph>() {
        @Override
        public MyGraph createNewGraph() {
            return new MyGraphImpl();
        }
    };

// method to inject our test instance into test classes
    @Contract.Inject
    public IProducer<MyGraph> getProducer() {
        return producer;
    }
}

Running this test will execute all of the contract tests that are applicable to the MyGraph class. If Apache Jena updates their tests, they will be executed. The MyGraph developer does not need to know that the Graph interface extends GraphAdd, nor is the developer concerned with the organization of the interfaces that Graph implements. The contract with the developer is that Apache Jena will provide contract tests that will test the Graph interface hierarchy. The advantage for the developer is that he/she does not have to respond to refactoring of the interfaces except where the signature of Graph methods change. Furthermore, since the contract tests are from the team that developed the interface, the developer can be fairly certain the tests are correct and that if the code passes them, it will function correctly in the environment.


Claude N. Warren, Jr. is an IT Architect and Java Developer with more than 20 years of development experience, he is also a committer on the Apache Jena project. Code discussed in this article is available under the Apache 2.0 license at https://github.com/Claudenw/junit-contracts.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video