Lamer than NUnit

I'm not yet a true convert of test-driven development -- at least, not for UI classes or code requiring complex I/O (the vast majority of my work).  But for simple, rock-bottom library classes (like the FloatComparer class I recently posted) I find unit testing to be indispensable.  (Not a panacea, but extremely useful nevertheless.)

Still, it can be a bit cumbersome -- and the more popular tools of the trade (like NUnit) tend to make the experience even more so.  NUnit makes my life difficult in the following ways:

Granted, these same basic complaints apply to any third-party component.  But NUnit is not a component!  It's a developer tool.  There's a difference between, say, a third-party grid control, which I may only reuse once in a blue moon (and then, usually for full-fledged development projects with unavoidably large and complex setups) and a tool or technology that's an intimate part of every little class I write.  I just don't wanna deal with it!  Indeed, it's the little standalone library classes like FloatComparer which lend themselves most readily to unit testing.

I see no reason for unit testing to complicate my build process or redist strategy.  Do we really need a full-fledged assembly reference to NUnit.Framework, just to get at... what?  A handful of attributes?  No -- the NUnit EXEs could just as easily look up the attributes by name, rather than by strong-typing.  I don't need strong-naming for my unit tests: names like NUnit.Framework.TestFixture aren't likely to collide with any other type names in my code!

Solution:  I've taken to simply defining my own unit test attribute, wherever I need it -- it's really just a handful of code.  I call my attribute [Jitsu.Testing.UnitTest] but your taste may differ.  I spent a few hours this past weekend and whipped up my own TestRunner tool which simply reflects for that attribute, by name, and then invokes any static methods which exhibit that attribute.  Crude, but effective!

#if TESTING
namespace Jitsu.Testing
{
  // Usage: tag unit test entry points with this attribute. 
  // Methods' signature should be of the form 'static bool Foo()'
  [AttributeUsage(AttributeTargets.Method)]
  class UnitTestAttribute : Attribute
  { }
}
#endif

You can sneak this small block of code into any project -- it's short enough to type by memory, or you can link to a common source file.  The point is, there are no third-party assemblies to ship, and it allows you to keep all your test goo neatly tucked away inside a #define block (out of your retail builds!) if you wish.

To make matters even simpler, my TestRunner tool wraps a custom TextWriter over the stderr stream, allowing unit tests to log a failure by simply logging a message to Console.Error.  So, a sample unit test method might look like this:

class Widget
{
  // (Ordinary class innards, here...)

#if TESTING
  [Jitsu.Testing.UnitTest]
  static bool TestWidget1()
  {
    Console.WriteLine("First test, s/b successful.");
    return true;//pass
  }

  [Jitsu.Testing.UnitTest]
  static bool TestWidget2()
  {
    Console.WriteLine("Second test, s/b failure.");
    Console.Error.WriteLine("Oops, something failed!");

    Console.WriteLine("Continuing second test.");
    Console.Error.WriteLine("Yikes, something else failed!");

    return false;//fail
  }
#endif
}

No specially defined exception types to worry about...  if an exception is expected, you can just catch it and get on.  If not, it will be caught and logged as a failure for that case.  You needn't even bother with the boolean return code if you don't want to (if you return 'true', the case will still be failed if you write anything at all to Console.Error).

Here's hoping others out there may find this crude-but-effective approach to unit testing useful -- if NUnit has been causing you headaches, possibly preventing you from adopting TDD methodologies, then hopefully this alternative will cure the pain.

Happy Testing!

Updated: Mon, 11 Oct 2004 18:01:54 GMT