Search code examples
c#unit-testinggeolocationtddgreat-circle

How to test something like the great circle distance calculation properly?


What is the best way to test something that does not equal exact values, like the great circle calculation:

/// <summary>
///  Get the great circle distance (shortest distance possible) between two points in km.
/// </summary>
/// <param name="endPoint">end point</param>
/// <returns>the great circle distance in km</returns>
public double GreatCircleDistanceInKm(IGeoPoint endPoint)
{
    var earthRadius = Constants.EARTH_RADIUS_KM;
    var diffLat = Utility.DegreesToRadians(endPoint.Latitude - this.Latitude);
    var diffLong = Utility.DegreesToRadians(endPoint.Longitude - this.Longitude);

    var a = Math.Sin(diffLat / 2) * Math.Sin(diffLat / 2) +
            Math.Cos(Utility.DegreesToRadians(this.Latitude)) * Math.Cos(Utility.DegreesToRadians(endPoint.Latitude)) *
            Math.Sin(diffLong / 2) * Math.Sin(diffLong / 2);
    var c = 2 * Math.Asin(Math.Min(1, Math.Sqrt(a)));
    var d = earthRadius * c;

    return d;
}

Currently my test is like this:

[TestMethod]
public void GeoPoint_GreatCircleDistanceInKm_IsCorrect()
{
    // arrange
    var startPoint = new GeoPoint(0, 45, 90); // id, lat, long
    var endPoint1 = new GeoPoint(0, 45, 90);
    var endPoint2 = new GeoPoint(0, 0, 0);

    // act
    var greatCircleDistanceZero = startPoint.GreatCircleDistanceInKm(endPoint1);
    var greatCircleDistanceBig = startPoint.GreatCircleDistanceInKm(endPoint2);

    // assert
    Assert.AreEqual(0, greatCircleDistanceZero);
    Assert.AreEqual(10007.543398010288, greatCircleDistanceBig);
}

But this seems wrong, I am finding the answer first then testing against it. How should such methods be tested? Should I go through the algorithm/calculation and try and find out how it works, so I can make it result in exact values?

Clarification: My question is this the correct method of conducting a test for such things, ie. Should my test bound to the actual implementation (because as you see I am using a fine-grained expected value) or should it be more generic somehow?


Solution

  • You can use Assert.AreEqual(double expected, double actual, double delta). You should use a delta that is sufficiently small (e.g. 0.00000001). Using double.Epsilon isn't recommended here.

    From MSDN:

    Because Epsilon defines the minimum expression of a positive value whose range is near zero, the margin of difference between two similar values must be greater than Epsilon. Typically, it is many times greater than Epsilon. Because of this, we recommend that you do not use Epsilon when comparing Double values for equality.

    To your other questions: Yes, such methods should be tested. But it is not a good idea to write tests after you inspected how the algorithm is implemented, because it may be implemented wrong. You need an overall idea what for example the method should be doing (in your case calculating the greate circle distance). Now you can specify test cases by specifying the input and the expected output. You can retrieve the expected output from another source (e.g. calculating it by hand).

    A little side note: In TDD test cases are usually specified before the actual code is written. So there is no algorithm you can go through and find out how it works.