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.
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'.
ReplyDeleteOr maybe just an extension method 'Be' that does nothing to satisfy pendants like myself :)
Mike +1
ReplyDelete@Mike
ReplyDeleteI 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
Should().Equal(...)
ReplyDeleteShould().Not.Equal(...) or ShouldNot().Equal(...)
Should() -> Is() ???
ReplyDeletesomething.Is().EqualTo(...)
something.Is().Not.EqualTo(...)
Not sure if that's an improvement, but could address some of the above comments. :)
I like to have the verb. It doesn't sounds good without it.
ReplyDeleteShould().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.
It seems awfully verbose...the conditions actually get in the background...
ReplyDeleteWhy not something based on lambdas?
Instead Of: DateTime.Today.Should().LessThan(DateTime.Now);
write:
CustomAssert(()=> DateTimeToday < DateTime.Now);
The condition is in foreground...
@Liviu
ReplyDeleteInteresting 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...
@David
ReplyDeleteSure your proposal is a short-cut.
Now try it with a string or a IEnumerable using Contains constraint.
My opinion:
ReplyDeleteShould().Be.Null
Should().NotBe.Null
Should().Null
"Be" could be optional (return this).
@Fabio,
ReplyDeleteYes, 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
Doesn't xUnit strive for this?
ReplyDelete@Bill
ReplyDeletexUnit has a syntax based on extensions ?
@Bill, @Maulo
ReplyDeleteI 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/
Really happy that you think of adding Should extensions to NUnit.
ReplyDeleteI 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");
new Action(() => new MyClass(null)).Should().Throw <ArgumentNullException >(x => x.Should().StartWith("The parameter..."));
ReplyDeleteThoughts ?
Well, what is x in .Throw <ArgumentNullException >(x => ...)?
ReplyDeleteThe 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?
BTW. My name is Lars (Corneliussen). Didn't see, that my wp-open-id doesnt show my name.
ReplyDelete