Extended Constraint Syntax

This specification is about a new syntax for expressing NUnit assertions using the features of .NET framework 3.5 including language improvements. There are two key features this enables: Extension methods permit a modular, extensible fluent constraint syntax, and LINQ expression trees permit using plain C# (or VB) as a expression-based constraint language.

Launchpad Blueprint: http://blueprints.launchpad.net/nunit-3.0/+spec/extended-constraint-syntax

User Stories

Fluent Constraint Syntax
  • A user expresses assertions in a fluent fashion
  • A user extends NUnit's constraints without modifying NUnit
  • An extension developer packages new constraints and their syntax in a single extension

This improves NUnit by making extensibility easier.

Expression-based Constraints
  • A user expresses assertions in C# or VB without needing to learn NUnit-specific constraints
  • Any boolean expression convertible to an expression tree can form a constraint
  • Failed assertions include detailed error messages such as subexpression values - i.e. unlike Debug.Assert.

This improves NUnit by making it easier to learn and less necessary to extend.

Existing Implementations

There are already a couple of implementations implementing a fluent syntax or expression-based syntax or both:

  • Extension Methods For Nunit: this is substantially an aliasing mechanism which, without touching the constraints themselves, provides a fluent way to express them
  • SharpTestsEx: this is an improvement of NUnitEx and provides an extension-method based constraint syntax in addition to a starting point for an expression-based mechanism of assertions.
  • ExpressionToCode: this is an expression-based assertion library. It is a reimplementation of Power Assert .NET, which is itself a port of Groovy's Power Assert.

Extensible Fluent Constraint Proposal

Ideally we would like to be able to use a constraint syntax similar to the current fluent syntax that is extensible. This requires using extension methods instead of static classes, as the following example demonstrates:

Assert.That(1, Is.GreaterThan(0))

This example is not extensible since it uses the static Is class. If I wanted to write something like Is.MuchGreaterThan(int x) and thus a constraint MuchGreaterThan which NUnit doesn't provide, I would have to alter and recompile NUnit's code. Patching NUnit like this and maintaining such a patch is a high barrier to entry.

The same assertion could be written as:

Assert.That(1).Is.GreaterThan(0)

This allows for extensions since any user can define a new extension method for the class of the Is property. Say Is were to return a value of type IIsConstraint, then I could just write

public static void MuchGreaterThan(this IIsConstraint iis) {  ...  }

Taking syntax shown on the SharpTestsEx homepage as an example, some assertions with the new NUnit syntax could be written as following:

SharpTestsEx NUnit 3.0 proposal
true.Should().Be.True(); Assert.That(true).Is.True
“somethig”.Should().Contain(“some”); Assert.That(“something”).Contains(“some”)
“somethig”.Should().StartWith(“so”).And.EndWith(“ing”) Assert.That(“something”).StartsWith(“so”).And.EndsWith(“ing”)
new[] {1, 2, 3}.Should().Have.SameSequenceAs(new[] { 1, 2, 3 }); Assert.That(new[] {1, 2, 3}).Is.EquivalentTo(new[] {1, 2, 3})
ActionAssert.Throws<ArgumentException>(() => new SillyClass(null)) Assert.That(() => new SillyClass(null)).Throws<ArgumentException>()

Unresolved Issues

How would the following actually work?

Assert.That(“something”).StartsWith(“so”).Or.EndsWith(“ing”);

How would StartsWith know that it's not supposed to do anything if it fails, since there is a chance EndsWith might succeed?

Remember, the above code is semantically identical to this:

var temp = Assert.That(“something”).StartsWith(“so”);
temp.Or.EndsWith(“ing”);

Extension methods can lead to namespace pollution; in particular if defined on object (which we therefore should try to avoid).

Expression Based Constraint Proposal

NUnit provides a wealth of constraints. This means that knowing how to expression non-trivial constraints isn't always easy (particularly for new or casual users). Finding the appropriate constraint (or combination of constraints) requires knowledge NUnit's many constraints, and the semantics of a particular constraint may not be clear without reading the documentation.

For example, consider the assertion (valid for many uppercase strings, for most cultures):

Assert.That(() => x == x.ToLower().ToUpper())

This expression can be expressed as a standard equality constraint, but doing so means not showing the intermediate steps in the computation. Using expression trees, a failure could be rendered as:

Assert.That failed for:
 
x == x.ToLower().ToUpper()
|  | |    |         |
|  | |    |         "ABC I"
|  | |    "abc i"
|  | "ABC İ"
|  false
"ABC İ"

This variant requires very little knowledge of NUnit, yet is still usable even for complex constraints by leveraging a language the user already knows (namely VB or C#).

Possible extensions to this concept could be “Helpers” that recognize specific patterns and improve readability. For instance, if an expression consists of a sequence of && operators, a helper might suppress showing the details of non-failing clauses. Or, if an expression contains multiple DateTime, the helper could ensure the accuracy of the DateTime.ToString is high enough to represent any differences. If an == operator fails but .Equals would have succeeded, this could be mentioned.


QR Code
QR Code dev:specs:extended_constraint_syntax (generated for current page)