Conformance tests

The GeoAPI conformance module provides three kinds of Java classes:

  • Validators for testing the conformance of existing instances of GeoAPI interfaces.
  • TestCase as the base class of all JUnit tests in this module, which can be extended by developers on a case-by-case basis.
  • TestSuite as the aggregation of all JUnit tests defined in this module.

The test cases and validators are grouped in util, metadata, referencing and geometry packages. A graphical JUnit runner is also provided in the runner package. That JUnit runner is designed specifically for the geoapi-conformance test suite, and provides more information than common runners (for example which factories were used for each test).

The GeoAPI conformance module provides also a partial implementation of Geospatial Integrity of Geoscience Software tests in its GIGS package.

Running all pre-defined tests

The easiest way to test an implementation is to add the following class in the implementation test package. There is nothing else needed if all factories are registered in the META-INF/services/ directory and all objects are fully implemented:

package org.myproject;

import org.opengis.test.TestSuite

/**
 * Executes all GeoAPI tests using the factories registered in the META-INF/services directory.
 * Every GeoAPI objects to be tested are assumed fully implemented. The implementation accuracy
 * is assumed good enough for the default tolerance thresholds.
 */
public class GeoapiTest extends TestSuite {
}

Configuring the tests

In the more common case where the implementor needs to configure some tests (skip some tests, relax some tolerance thresholds, etc.), the above class can implements the ImplementationDetails interface. The following example declares that the tolerance factor for MyProjection needs to be relaxed by a factor 10 during inverse projections, and that the derivative functions of math transforms are not yet implemented.

package org.myproject;

import org.opengis.test.*;
import org.opengis.util.Factory;
import org.opengis.referencing.operation.MathTransform;
import java.util.Properties;

/**
 * Executes all GeoAPI tests using the factories registered in the META-INF/services directory.
 * All MathTransform.derivative(DirectPosition) tests are skipped, and the tolerance threshold
 * for MyProjection latitude values is relaxed by a factor of 10 during inverse projections.
 */
public class GeoapiTest extends TestSuite implements ImplementationDetails {
    private static final Configuration CONFIGURATION = new Configuration();
    static {
        CONFIGURATION.unsupported(Configuration.Key.isDerivativeSupported);
    }

    /**
     * Returns the enabled/disabled state of tests, or null to keep all tests enabled.
     */
    @Override
    public Configuration configuration(Factory... factories) {
        return CONFIGURATION;
    }

    /**
     * Returns an object for modifying the tolerance thresholds when testing the given
     * math transform, or null if no change is needed.
     */
    @Override
    public ToleranceModifier tolerance(MathTransform transform) {
        if (transform instanceof MyProjection) {
            return ToleranceModifiers.scale(EnumSet.of(CalculationType.INVERSE_TRANSFORM), 1, 10);
        }
        return null;
    }
}

The fully qualified name of the above class must be declared in a META-INF/services/org.opengis.test.ImplementationDetails file in order to instruct GeoAPI to invoke the above methods.

Writing custom tests

Implementors can either write their own JUnit tests, or extend some of the existing implementations. With the later approach, implementors can override the existing test methods for finer control on the tests being executed.

In every cases, implementors can use the  static methods for testing their implementation consistency. For example an implementor can test the validity of his Coordinate Reference System objects and related objects in a test suite like below:

package org.myproject;

import org.junit.*;
import static org.opengis.test.Validators.*;

public class MyTests {
    @Test
    public void testMyCRS() {
        CoordinateReferenceSystem crs = ...
        validate(crs);

        MathTransform transform = ...
        validate(transform);
    }
}

Validators are thread-safe except for the configuration phase (which is optional and usually executed only once before the tests begin). Validators test the logical consistency of their argument. For example if given a chain of concatenated transforms, validate(object) will ensure that the source dimension of a transformation step is equals to the target dimension of the previous step. Dependencies are traversed recursively, for example validating a CoordinateReferenceSystem object implies validating its CoordinateSystem and Datum attributes.