Tuesday, June 25, 2013

Improving the Readability of Windows Store Unit Tests

I’ve grown to appreciate the value that testing adds to software development. In my opinion many developers spend far too much time focusing on the red herring of code coverage or writing tests just to check them off, rather than the real benefits they provide when you take a more Test-Driven Development (TDD) approach. Although I’ll be the first to admit I don’t follow strict TDD in all of my projects, I have found that having the discipline to write your tests first as often as possible can save a lot of time on a project and result in a much cleaner API. It’s the difference between building a class and exposing what you think the API should look like, versus writing a test that uses the API the way you would want to consume it, then writing the class to fulfill the need.

One issue I have with tests, however, is that the Microsoft method for testing isn’t very clear. For example, take a look at this:

Assert.IsTrue(2 > 3);                 

How would you describe this test to someone? “assert it is true 2 is greater than three” is how it reads. How about this instead?

Assert.That(2, Is.GreaterThan(3));

That now reads, “assert that 2 is greater than 3”. Doesn’t that describe exactly what you are checking (and hoping will fail?) This becomes even more confusing when you are comparing equality. Try this:

Assert.AreEqual(2, 3);

Compared to this:

Assert.That(2, Is.IsEqualTo(3));

Again, you may think I’m crazy but I think the second is easier to write and read. It just flows and instead of trying to figure out which assertion method makes sense, then putting the expected or actual values in the right place, etc. why not just write it out in a nice, easy to understand sentence?

It turns out it is fairly easy to build your own library of extensions to the existing framework that make it easy to provide a more fluent interface for testing. There are third-party libraries you can look into but the quick and dirty way is right here. I just decided to ride on the truth assertion and extend that to fit my needs. First, a class that will take some generic object and apply a predicate then optionally display a message:

public static class AssertIt
{
    public static void That<T>(T target, Func<T, bool> condition, string message = ""
    {
            if (string.IsNullOrWhiteSpace(message))
            {
                Assert.IsTrue(condition(target));
            }
            else
            {
                Assert.IsTrue(condition(target), message);
            }
    }       
}

Next, a static class for the comparisons. Note the parent class is expecting a predicate, so the helper class will provide the predicate and manage any optional parameters. Here is the “Is” class with two examples:

public static class Is
{
    public static Func<T, bool> GreaterThan<T>(T comparison) where T : IComparable
    {
        return target => target.CompareTo(comparison) > 0;
    }

    public static Func<string, bool> NotNullOrWhitespace()
    {
        return target => !string.IsNullOrEmpty(target);
    }
}

Each method returns the predicate for the comparison and handles any additional parameters that are needed. This enables you to write something like this:

AssertIt.That(2, Is.GreaterThan(3));                 

Of course we need to test that our tests are going to run appropriately! Here’s a pair of tests that ensure our assertions are working correctly for the “greater than” comparison.

[TestMethod]
public
 void GivenAnIntegerWhenComparedGreatherThanToAGreaterIntegerThenShouldFail()
{
    var exception = false;

    try
    {
        AssertIt.That(2, Is.GreaterThan(3));                
    }
    catch (AssertFailedException)
    {
        exception = true;
    }

    Assert.IsTrue(exception, "Test failed: an exception should have been thrown.");
}

[TestMethod]
public
 void GivenAnIntegerWhenComparedGreaterThanToALesserIntegerThenShouldSucceed()
{
    AssertIt.That(3, Is.GreaterThan(2));            
}

And that’s it – to me it’s worthwhile to invest some time in some extension and helper methods to make the code far more readable and easier to write. You could, of course, just plug in a third-party test framework that already provides a fluent interface like NUnit as well.

No comments:

Post a Comment