junit-contracts: A suite of tools to make contract testing easier
Maven Repository Info
Group Id: org.xenei Artifact Id: contracts
Snapshot versions are hosted at:
The contracts project comprises three modules: junit-contracts, contract-test-maven-plugin and contract-cmdline.
junit-contracts is the module that encompasses the annotations and junit test runners to execute the connection tests.
Group Id: org.xenei Artifact Id: junit-contracts
maven-contracts is the module that encompasses the maven plugin that will report results and errors from running the tests.
Group Id: org.xenei Artifact Id: contract-test-maven-plugin
contracts-cmdline is the model that encompasses the command line tools that will generate reports equivalent ot the maven contracts plugin.
Group Id: org.xenei Artifact Id: contract-cmd-line
The junit-contracts module provides a set of classes to discover and filter classes on the classpath. These may be generally useful. Among these is the ClassFilter which is patterned after the Java FileFilter class.
Class filters are constructed in the maven and command line modules via strings. Each string is the string representation of a filter. For example: Not( Wildcard( "Test" ) ) will match any class name that does not have the string "test". For more information see the Class Filters page.
Contract tests test the contract defined by a Java interface and the associated documentation. The interface defines the method signatures, but the documentation often expands upon that to define the behaviour that the interface is expected to perform. For example, the Map interface defines put(), get(), and remove(). But the human readable documentation tells the developer that if you put() an object you must be able to get() it unless the remove() has been called. That last statement defines two tests in the contract test.
The basic argument for the use of contract tests is that you can prove code correctness. That is, if every object interface is defined as an interface, every interface has a contract test that covers all methods and their expected operation, and all objects have tests that mock the objects they call as per the interface definition - then running the entire suite of tests demonstrates that the interconnection between each object works.
If we know that A calls B properly and B calls C properly then we can infer that A calls C properly. We can, with some work, prove that the code is correct.
Contract tests will not discover misconfiguration. For example, if class A uses a map and expects a map that can accept null keys but the configuration specifies a map implementation that does not accept null keys, the contract test will not detect the error. But then the error is a configuration error caused by a missing requirement for the configuration.
For more information on contract tests see
How it Works
A | ---+---. | | A1 A2
Most JUnit tests work vertically. Originally they were all derived from a test class, and since version 4 they use annotations to identify a method as a test. However, they only work in the direct hierarchy of class inheritance. That is, if classes A1 and A2 are derived from class A and tests A1Test and A2Test are derived from ATest then all the ATest unit tests will be executed when A1Test and A2Test are run. However, with the use of interfaces there arises an orthogonal testing problem.
A | ---+---. | | A1(I) A2 B(I)
If we add interface I to our class structure and define A1 as implementing I and a new class B as implementing I, then both A1 and B require the I tests, but they have no classes in common in the hierarchy. Therefore the tests for A1 and B must implement them - this violates the Don't-Repeat-Yourself (DRY) principle and will lead to problems if I changes and the A1 and B tests are not modified as well.
The ContractSuite solves this problem by requiring the following additions:
- an abstract test class ITest for the interface I, that is annotated with the @Contract( I.class ).
- a contract test suite that is annotated with @RunWith( ContractSuite.class ) and implements method annotated with @Contract.Inject that creates a Producer.
Then when executing the suite:
- discovers all of the @Contract mappings between tests and interfaces.
- finds all the implemented interfaces of the object under test and adds their contract tests to the suite.
- executes the completed suite.
The result is that every class that has a contract suite defined will verify that its implementation of the interface is correct as per the contract test. If the contract test changes, the class test will use the new contract test implementation.
Samples and Examples
The test code contains a number of examples and is well documented for use as such.
The sample code tree includes a sample for a Serializable contract test.