Tuesday, November 24, 2015

NUnit assertions - constraint model and classic model

NUnit assertions - constraint model and classic model

If you are using NUnit for your unit tests, there are two models for writing assertions. The "old skool" way of writing assertions is like

Assert.IsTrue(a == 5);
Assert.AreEqual(expectedValue, result);

This way of writing assertions is well known and is used in most unit testing frameworks. Some years ago, NUnit started introducing a new assertion model.

Constraint model

The equivalent assertions in the new constraint model are

Assert.That(a, Is.EqualTo(5));
Assert.That(result, Is.EqualTo(expectedResult);

When the constraint model was introduced, it seemed like nothing more than syntactical sugar. I didn't see any real benefit to it and continued using the classic model.

The constraint model has however been refined and extended over the past years. New and clever ways of writing assertions have been introduced to the constraint model, while the classic model has not evolved.

Rather than writing complex asserts, there is now a number of useful asserts on collections like

Assert.That( iarray, Is.All.GreaterThan(0) );
Assert.That( myArray, Is.SubsetOf( largerArray ) );
Assert.That( sarray, Is.Ordered.Descending );
Assert.That( sarray, Has.No.Member("x") );
Assert.That( iarray, Has.Some.GreaterThan(2) );
Assert.That( iarray, Is.All.GreaterThan(0) );

The meaning of these asserts are pretty self-explaining because of the new syntax.

The constraints typically have additional modifiers. For example, for comparing two doubles, one would often provide a tolerance in the classic model in order to cope with floating-point round-off errors:

Assert.AreEqual(expectedValue, result, 0.000001)

Where the tolerance often was somewhat arbitrary and dependent on the expected value.

With the new constraint model, this can be made much more robust by specifying it like

Assert.That(result, Is.EqualTo(expectedResult).Within(0.01).Percent
Assert.That(result, Is.EqualTo(expectedResult).Within(2).Ulps

The "Ulps" tolerance is quite interesting. "Ulp" means "Units in the Last Place" and is more robust for allowing numerical inaccuracies that are introduced than using a fixed tolerance.

The list of constraints is too long to replicate here. The full set of constraints is listed in the NUnit documentation here: https://github.com/nunit/nunit/wiki/Constraints.

Custom constraints

If you happen to write your own classes (!), you may need to be able to compare them using a less strict method than the Equals(). For example, you may need to compare mathematical vectors within a tolerance.

Rather than using three asserts for a 3-dimensional vector, you can write your own constraints with a given tolerance.

The classic model is disappearing

Until now, NUnit has been fully backwards compatible. The developer has had the choice to use the classic model if desired. Starting in version 3.0, however, some of the classic assertions have disappeared. For example, the Assert.IsNullOrEmpty() has been removed.

I don't know if the classic assertion model will eventually be completely retired. Who cares anyway, when the constraint model is so much better? ;-)

No comments:

Post a Comment