JUnit – the Difference between Practice and @Theory

JUnit – the Difference between Practice and @Theory

Lately a colleague showed me how to improve JUnit tests written for a distance calculator. Speaking with other developers I found out that the majority wasn’t aware of the undocumented @Theories Runner which can be found in an experimental package in JUnit, so I decided to share this valuable “experiment”.

In contrast to the parameterized JUnit test, the Theories-runner will try out all possible combinations of data points against your test methods marked with the @Theory annotation.

But let’s get your hands dirty. Obviously the first thing to do is run the test with the Theories-runner and declare some data points:

public class DistanceCalculatorTest {

  static public LatLng es_1 = new LatLng( 49.0060285, 8.4006111 );
  static public LatLng es_2 = new LatLng( 49.0060285, 8.4005789 );
  static public LatLng es_3 = new LatLng( 49.0060056, 8.4005611 );

The implementation of the distance calculator should be:

  • Commutative,
  • Positive semidefinite
  • And fullfill the triangle equality

Which can be written down using the following three @Theory annotated test methods (which do not accept null values):

@Theory(nullsAccepted = false)
public void distanceIsCommutative(LatLng p1, LatLng p2) {
    assertThat(distanceCalculator.calculate(p1, p2), is(distanceCalculator.calculate(p2, p1)));

@Theory(nullsAccepted = false)
public void distanceIsPositiveSemidefinite(LatLng p1, LatLng p2) {
    assertThat(distanceCalculator.calculate(p1, p2), is(greaterThanOrEqualTo(0.)));

@Theory(nullsAccepted = false)
public void distanceFulfillsTriangleEquality(LatLng p1, LatLng p2, LatLng p3) {
    assertThat(distanceCalculator.calculate(p1, p2) + distanceCalculator.calculate(p2, p3), 
        is(greaterThanOrEqualTo(distanceCalculator.calculate(p1, p3) - delta)));

Sometimes you have to assume preconditions to test specific parts of your implementation. In the following @Theory the assumption is either p1 or p2 is null, which should lead to an IllegalArgumentException when using the calculator…

public static LatLng nullPoint = null;

public ExpectedException distanceCalculatorException = ExpectedException.none();

public void distanceToNullNotDefined(LatLng p1, LatLng p2) throws Exception {
    assumeTrue(p1 == null || p2 == null);
    distanceCalculator.calculate(p1, p2);

It is very simple to add more corner cases. The following snippet adds two representations of the north pole:

static public LatLng north_pole_1 = new LatLng( 90, 10 );
static public LatLng north_pole_2 = new LatLng( 90, 20 );

If you add a test which won’t work with the whole data points available (e.g. near the poles in this example) they can be easily filtered out by adding a more complex assumption at the beginning of the test method.

@Theory(nullsAccepted = false)
public void runningTowardsThePole(LatLng pt) {

    // this test does not work at the poles
    assumeThat(pt.lat, is(allOf(greaterThan(-89.), lessThan(89.))));

Just a side note. Rewriting the tests for the distance calculator in fact discovered a bug in the original implementation of the distance calculator using LatLng(-1, -1) as NIRVANA. Thanks again Hauke for teaching me this lesson.

Have fun verifying your code with theories.

1 Comment
  • davidB
    Posted at 1:58 pm, April 11, 2013


    Nice to know this features is (will be) available in JUnit.
    Lot of inspiration from QuickCheck ( http://www.cse.chalmers.se/~rjmh/QuickCheck/)

    in scala : https://github.com/rickynils/scalacheck
    may be a java port : https://bitbucket.org/blob79/quickcheck