Annotating Your Code with Simple Tests

Simple Tests, in XML Comments

Another idea: what if you annotated your functions with notes that say exactly what the most interesting edge cases are for that function.

Basically at the top of each method you could specify a few test cases. And only where you really need them. It's kind of a minimalists approach to test driven development.

Here's an example function -- leap year. With a few deliberate bugs in it.

continues...

    /// <summary>

    /// Is Year a leap year?

    /// </summary>

    /// <param name="Year">The year in question</param>

    /// <returns>

    /// true if year is a leap year. false otherwise

    /// </returns>

    private bool IsALeapYear(int Year)

    {

        return (Year % 4 == 0);

    }

Now the weird thing about 'leap years' is that every hundred years we skip a leap year. And every four hundred years we skip skipping a leap year.(I think that's it?) Anyway -- this means that a few simple tests will pick up the most common mistakes.

    /// <summary>

    /// Is Year a leap year?

    /// </summary>

    /// <param name="Year">The year in question</param>

    /// <returns>

    /// true if year is a leap year. false otherwise

    /// </returns>

    /// <testCase Year="2000" return="true"/> (a standalone test case)

    /// <testCase Year="1900" return="false"/> (another standalone test case)

    /// <testCase Year="1996" return="true"/> (a few more normal cases)

    /// <testCase Year="2003" return="false"/> (a few more normal cases)

    /// <testCase Year="1973" return="false"/> (and so on...)

    private bool IsALeapYear(int Year)

    {

        return (Year % 4 == 0);

    }

So in the sample above we've used XML Comments to indicate some very simple tests that would find the most common mistakes in such a function.

We then run some nifty little tool that inspects our code comments, generates the test code, runs it, and tells us how we went. "Oh we failed for this value? Better check out my code..."

(Note, i've used XML Comments rather than 'code attributes' [i.e. the code decorations you wrap in square brackets for C#, or angle brackets for VB.net] so that the units tests wouldn't need to be shipped with your assembly.)

I often take these ideas too far. So I want to pretty much stop there.

Part of me is screaming though, saying "That's so obvious!! Why can't we do that today!! That should be built into all languages!!" And I'm certain that other, smarter devs, have described this exact same system before. There's probably a university course on it somewhere. A lecture circuit ad so on)

(Quick shout out -- I'm using the excellent GhostDoc here (as I do everyday). And using Copy Source as Html. What a tool!)

Setup/Tear Down

Two aspects (intentional pun ;-) of Nunit are the setup and tear down for the test and test-fixture.

Maybe you could specify a particular setup for a test, like so:

    /// <summary>

    /// Gets the full name.

    /// </summary>

    /// <value>The full name.</value>

    /// <testCase return="Fred Smith" setup='PreparePerson("Fred","Smith");' />

    public string FullName

    {

        get { return String.Concat(m_FistName, " ", m_LastName);}       

    }

The idea of the snippet above is that the 'PreparePerson' function is called, and the test is then performed on the output of that function -- or something like that. In the same scope as that function, maybe. It's a bit of a stretch and requires a little bit of magic.

Tear down (i.e. a function that gets called after a test has run) would be specified in the same way.

Moving past that -- what if you have a bunch of tests that all require one function to be run first (this is more like a test-fixture setup in Nunit). Maybe you could do something like this:

    /// <summary>

    /// If person was born on birthdate, what's their age, in whole years at atDate?

    /// </summary>

    /// <param name="birthDate">The birth date.</param>

    /// <param name="atDate">At date.</param>

    /// <returns>Age in years (floored)</returns>

    /// <tests>

    ///  <setup>DateTime d = new DateTime(2006,09, 26)</setup>

    ///  <testCase birthDate="new DateTime(2006, 09, 25)" atDate="d" return="0" />

    ///  <testCase birthDate="new DateTime(2005, 09, 26)" atDate="d" return="1" />

    ///  <testCase birthDate="new DateTime(2005, 09, 26)" atDate="d" return="1" />

    ///  <testCase birthDate="new DateTime(2003, 09, 23)" atDate="d" return="3" />

    /// </tests>

    public int AgeAt(DateTime birthDate, DateTime atDate)

    {

        return atDate.AddYears(-1 * birthDate.Year).Year;

    }

More Complex Cases...

What if you wanted to write bigger test fixtures that wrap around more than one method?

In that case, i guess you've moved beyond the type of testing that this approach could suit. You could use nunit i guess, or mbUnit or some other integration suite style test.

Here's something else... say if you started with some simple tests as shown above, and then needed to do some more complex test, using NUnit. Well, there's no technical reason you couldn't have a right click option that refactors the tests above into their own Nunit test fixtures, say.

Here's a dodgy mockup...

refactor simple tests into NUnit tests

Any comments, insults, pointers to further info... please respond.

 

My book "Choose Your First Product" is available now.

It gives you 4 easy steps to find and validate a humble product idea.

Learn more.

(By the way, I read every comment and often respond.)

Your comment, please?

Your Name
Your Url (optional)
Note: I may edit, reuse or delete your comment. Don't be mean.