Try fast search NHibernate

10 March 2009

NUnitEx : actual.Should().EqualTo(expected)

After this thread, I have begun a new project on GoogleCode: NUnitEx.

The intention of the project is to be a “base-line” of a possible API implementation for NUnit3.0. As me, Charlie Poole, think that  “to have the code available is different than few-words and with this project we can "test" the acceptance of a possible API based on .Net3.5” (the phrase is mine and Charlie simply said “great!”).

NUnitEx is working on-top of NUnit, so you can mix NUnit classic assertions (Assert.That(…)) with NUnitEx assertions.

The short story

The first problem was define which should be the entry point… exactly… Should(), Should().Be, ShouldBe(), Should().Not.Be, ShouldBe().Not, Should().Contains, Should().EqualTo(exepected).WithMessage(message), Should().EqualTo(exepected).Because(message) and so on. The decision I have taken, so far, is: make it less verbose as possible maintaining Should() as the entry point.

The second problem was how much complicated should be the implementation. I have studied some NUnit2.4 internals and then I have compared it with NUnit2.5… well… to be short, the decision I have taken is: make it less complicated as possible and use NUnit from a point more external as possible.

Highlight

NUnit assertions/constraints are generics and polymorphic; generics because you can apply the same to everything (or near to it), polymorphic because the same constraint may change its behavior depending on the System.Type of actual/expected. Using NUnitEx this is not ever possible.

Take a look to the follows classic assertions :

Assert.That(true, Is.Null);
Assert.That(new object(), Is.True);

You can’t write the same using NUnitEx simply because an System.Boolean can’t be null or an System.Object can’t be a System.Boolean (the intellisense is your friend).

State of the art

Commons, and more used, constraints are implemented and the framework is usable.

So far the code-coverage is 100%.

Before begin the full implementation, of all constraints, a refactoring is needed in order to manage a more deeper methods-chaining leaving all methods with, at most, one parameter.

The discussion over the API is open; feel free to express your opinion.

To run NUnitEx tests you need to download the last available release of NUnit-Gui (2.5.0) available here.

When the heavy work will be done I’m planning to open a branch to support NUnit2.4.8.

Examples

DateTime.Today.Should().LessThan(DateTime.Now);
(5.0).Should().EqualTo(5);
((uint)10).Should().GreaterThan((short)4);
5.Should().LessThan(10);
(23.0m).Should().LessThan(double.MaxValue);

int? negableInt = 10;
negableInt.Should().EqualTo(10);
5.Should().LessThan(negableInt);
11.Should().GreaterThan(negableInt);
negableInt.HasValue.Should().True();
"a".Should().EqualTo("a");
"a".Should().Not.Empty();
"a".Should().Not.Null();
"".Should().Empty();
"".Should().Not.EqualTo("a");
((string) null).Should().Null();
var ints = new[] {1, 2, 3};
ints.Should().SequenceEqualTo(ints);
ints.Should().Not.SequenceEqualTo(new[] { 3, 2, 1 });
ints.Should().Not.Null();
ints.Should().Not.Empty();
(new int[0]).Should().Empty();
((IEnumerable<int>) null).Should().Null();
var ints = new[] { 1, 2, 3 };
ints.Should().Contains(2);
ints.Should().Not.Contains(4);

Where find NUnitEx

The project is really fresh, bins and sources are available here.


kick it on DotNetKicks.com

18 comments:

  1. Looks great, just a comment about the naming. If the entry point is going to be 'Should', then 'Equals' would be better than 'EqualTo'. 'Should Equal To' is not good English, but 'Should Equal' is. Similarly 'EqualTo' should be changed to 'BeEqualTo', 'SequenceEqualTo' to 'BeSequenceEqualTo', 'Null' to 'BeNull', 'Empty' to 'BeEmpty'.

    Or maybe just an extension method 'Be' that does nothing to satisfy pendants like myself :)

    ReplyDelete
  2. @Mike
    I know. The matter is try to use it in VS.
    ShouldBe is good until you don't need the negation ShouldBe().Not is not so good.
    Should().Be.Not.Equal(...) is something boring to write as Should().Be.Equal . Should().BeEqual : I like it even if before have intellisense working you must write "Be".
    @Olof
    Do you can specify "+1" to which of Mike proposal ?

    http://groups.google.com/group/nunit-discuss/browse_thread/thread/5bfd4d2c1a4dcffb

    ReplyDelete
  3. Should().Equal(...)
    Should().Not.Equal(...) or ShouldNot().Equal(...)

    ReplyDelete
  4. Should() -> Is() ???

    something.Is().EqualTo(...)
    something.Is().Not.EqualTo(...)

    Not sure if that's an improvement, but could address some of the above comments. :)

    ReplyDelete
  5. I like to have the verb. It doesn't sounds good without it.

    Should().Be.Equal
    Should().Not.Be.Equal

    Or To type less:
    Should().BeEqual
    Should().NotBeEqual

    The problem with the last approach is that intelisense will group all the methods together
    BeEqual
    BeLess
    BeMore

    etc...

    So I guess the first approach makes more sense.

    Gracias por tomar el tiempo de hacer esto.
    Hernan Garcia.

    ReplyDelete
  6. It seems awfully verbose...the conditions actually get in the background...

    Why not something based on lambdas?
    Instead Of: DateTime.Today.Should().LessThan(DateTime.Now);

    write:
    CustomAssert(()=> DateTimeToday < DateTime.Now);

    The condition is in foreground...

    ReplyDelete
  7. @Liviu
    Interesting point but should look like
    Assert.That(() => actual < expected);
    but there are other assertion where e extensions are betters
    actual.Should().Serializable();

    @ All
    I'm still thinking about
    Should().Be.Null or Should().BeNull
    where the negation look like
    Should().Not.Be.Null or Should().Not.BeNull

    For BeXYZ the problem is the intellisense...
    In practice after .Sh (here ')' to auto complete and close) I click '.' and the first letter of constraint (for example 'n' for Null) and then again ')'... the result is .Should().Null()

    quick typing...

    ReplyDelete
  8. @David
    Sure your proposal is a short-cut.
    Now try it with a string or a IEnumerable using Contains constraint.

    ReplyDelete
  9. My opinion:

    Should().Be.Null
    Should().NotBe.Null
    Should().Null

    "Be" could be optional (return this).

    ReplyDelete
  10. @Fabio,

    Yes, using Is() would mean probably mean multiple entry points (Has() or Does()). I find this confusing with the current NUnit constraints, so this is probably bad.

    With regards to the current talk over Should().Be etc, maybe multiple entry points would work ok in this case?

    ShouldBe().Equal
    ShouldBe().LessThan
    ShouldNotBe().Equal
    Should().Contain
    ShouldBe().Serializable

    ReplyDelete
  11. @Bill
    xUnit has a syntax based on extensions ?

    ReplyDelete
  12. @Bill, @Maulo
    I think they once had. But now you can find them in Björn Rochel's xunit.BDDExtensions

    http://xunitbddextensions.googlecode.com/svn/trunk/Source/xUnit.BDDExtensions/BDDExtensions.cs

    http://www.bjoernrochel.de/2008/10/04/introducing-xunitbddextensions/

    ReplyDelete
  13. Really happy that you think of adding Should extensions to NUnit.

    I have dozens of Should* commands for NUnit and I'm happy with them. I don't think it's practicable to have proper english for all of it. That would then be just having dummy extension-methods for any verb :-)

    ShouldEqual
    ShouldNotEqual
    ShouldContain
    ShouldBeBetween

    Type ShouldEqual<TExpected>(this Type actual)
    ShouldBeAssignableTo<TExpected>(this Type actual)
    DateTime ShouldBeArround(this DateTime actual, DateTime expected, TimeSpan tolerance)

    TExpected ShouldBeOfType<TExpected>(this object actual)

    ...

    This makes it easy to chain expectations like this:

    myObject.ShouldBeOfType<int>.ShouldBeGreaterThan(5);

    new Action(() => throw new Exception("xy")).ShouldThrow<Exception>().Message.ShouldEqual("xy");

    ReplyDelete
  14. new Action(() => new MyClass(null)).Should().Throw <ArgumentNullException >(x => x.Should().StartWith("The parameter..."));

    Thoughts ?

    ReplyDelete
  15. Well, what is x in .Throw <ArgumentNullException >(x => ...)?

    The message? Might want to test other properties, too?

    As I said. I think it has too many dots. Maybe it is somehow faster to type, when doing "sh)th)..". But who does? Should is written quite fast. An then you'd have all the Should*s with intellisense, too.

    Thoughts?

    ReplyDelete
  16. BTW. My name is Lars (Corneliussen). Didn't see, that my wp-open-id doesnt show my name.

    ReplyDelete