I love NUnit's constraint-based API. I often use floating point comparison like this:
double d = foo.SomeComputedProperty;
Assert.That(d, Is.EqualTo(42.0).Within(0.001));
Very readable!
However, if I have a custom class whose equality depends on floating point comparison:
class Coord
{
Coord(double radius, double radians)
{
this.Radius = radius;
this.Radians = radians;
}
double Radius { get; }
double Radians { get; }
public override bool Equals(Object obj)
{
Coord c = obj as Coord;
if (obj == null || c == null) return false;
return c.Radians == this.Radians && c.Radius == this.Radius;
}
}
I would like to write my tests like this:
Coord reference = new Coord(1.0, 3.14);
// test another Coord for near-equality to a reference Coord:
Assert.That(testCoord, Is.EqualTo(reference).Within(0.001));
Is it at all possible to use NUnit like this?
The following is written for NUnit 3
Using the System.Numerics.Complex
class as a simple example, I can write a test
Assert.That(z1, Is.EqualTo(z2).Using<Complex>(NearlyEqual));
with this comparison function:
internal static bool NearlyEqual(Complex z1, Complex z2)
{
return Math.Abs(z1.Real - z2.Real) < 1e-10 &&
Math.Abs(z1.Imaginary - z2.Imaginary) < 1e-10;
}
This gives you the Comparer that was mentioned in juharr's comment, and achieves the basics of what you asked. It doesn't let me parameterise the tolerance, but it's close.
In order to complete the requirement, I would instead want to write the test as
Assert.That(z1, Is.EqualTo(z2).Within(new Complex(1e-10, 1e-10)));
but, as noted in the question, it's not as easy. It requires a new constraint extension method
public static class ComplexTestExtensions
{
public static ComplexEqualConstraint WithinZ(this EqualConstraint constraint, Complex tolerance)
{
return new ComplexEqualConstraint(constraint) { Tolerance = tolerance };
}
}
and then write the custom constraint along these lines:
public class ComplexEqualConstraint
: EqualConstraint
{
public ComplexEqualConstraint(EqualConstraint that)
: base(that)
{
}
public override ConstraintResult ApplyTo<TActual>(TActual actual)
{
bool success = false;
if (actual is Complex z1)
{
Complex z2 = (Complex)this.Arguments[0];
success = Math.Abs(z1.Real - z1.Real) < Tolerance.Real &&
Math.Abs(z1.Imaginary - z2.Imaginary) < Tolerance.Imaginary;
}
return new ConstraintResult(this, actual, success);
}
public new Complex Tolerance
{
get;
set;
} = Complex.Zero;
}
Based on ClickRick's answer:
If you need something that works with NUnit 2.6 (which for instance Unity's test framework is based on):
public static class ColorTestExtensions
{
public static EqualConstraint WithinManhattanDistance(this EqualConstraint constraint, float tolerance)
{
return new ColorEqualConstraint((Color) constraint.Arguments[0]).Within(tolerance);
}
private class ColorEqualConstraint : EqualConstraint
{
public ColorEqualConstraint(Color expected) : base(expected) {}
public override ConstraintResult ApplyTo(object actual)
{
if (!(actual is Color c1)) return new ConstraintResult(this, actual, false);
var c2 = (Color) Arguments[0];
var tolerance = (float) Tolerance.Value;
var success = ColorExtensions.ManhattanDistance(c1, c2) < tolerance;
return new ConstraintResult(this, actual, success);
}
}
}
This lets you write:
Color color = GetPixel(0,0);
Assert.That(color, Is.EqualTo(Color.red).WithinManhattanDistance(0.1f);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With