Wednesday, 31 March 2010

F# pwns your language!!!

I've been spending a little time starting to get a feel for F# recently and just discovered that it has the keyword "pown" :)

Tuesday, 23 March 2010

TDD - Test Deriven* Development, or how I used reflection to subvert everything test-first stands for.

(* Ok, so deriven isn’t a proper word, but it flows better than derived in that context)

In my efforts to improve myself as a developer, one of the key techniques that I’ve picked up on has been that of test driven development. A short time working with it has shown me that it helps you to think about how best to design the interface to the code you are focussing on, and that by endeavouring to write testable code, you end up with looser coupling and better adherence to good object oriented programming techniques. However, there can be times when it’s not the right tool for the job.

I was recently tasked with creating the logging system for the project that I am working on. There are currently 26 different events that create log entries, all of which share a common base structure of mandatory fields, along with a selection of other fields specific to the event. Some events are closely related so share a number of these extra fields, and some of the fields only appear in one event. The design that I was given involved creating a base class to hold the common content and allow polymorphism in certain areas of handling the logs, and derived classes for all the actual events. Each class has a constructor containing all of the fields to ensure that they are set and nothing is accidentally left null as the compiler will catch such issues. In hindsight, I feel that this would have been an ideal project for code generating from some T4 templates or something like that, however I only read my first article on T4 after starting the task. D’oh. So, I ended up with a lot of similar classes and felt a little concern that having done soooo much boring manual labour I had probably made a few mistakes that might be tricky to catch.

This struck me as the sort of area that a decent set of unit tests would be ideal for, but manually crafting such a thing would be as error-prone as writing the tests in the first case. This is where reflection came in. By scanning my assembly for all of the classes deriving from the base I was able to write a few simple tests to check for the sort of problems that I was concerned about. The tests I settled on were checking that the classes all had only 1 public constructor, and that each parameter in the constructor had a matching property on the class with the same name and the same data type to catch missing fields, typos, and mismatches such as nullable ints becoming normal ints etc. Lo and behold, my classes had a bunch of these small problems floating around which I was quickly able to correct, but that would have been a nightmare to find and fix manually.

Properly written unit tests should test only one thing, but obviously that is not possible with a reflection based test. I was able to write my code in such a way that I could test all classes for a single type of issue, and have the error message build a list for all failing classes, which seemed to me to be the best compromise. To do this I used the following methods as my mini reflection testing framework. The core of the functionality is RunReflectionTest, which takes an action that will be the specific test that will show up in the runner. This simply loops through all of the classes, running the provided action, whilst building up an error list, then displaying any problems at the end.

private void RunReflectionTest(Action<Type, List<string>> testMethod)
{
    var classes = GetAllEventClasses();
    var errorMessages = new List<string>();
    foreach (Type eventClass in classes)
    {
        int messageCount = errorMessages.Count;
        testMethod(eventClass, errorMessages);
        if (errorMessages.Count > messageCount)
            errorMessages.Add(Environment.NewLine);
    }
    DisplayErrors(errorMessages);
}

The DisplayErrors method simply checks to see if there are any entries in the list of error messages and calls Assert.Fail if necessary. As this was a quick system knocked up just to test a single area the tracking of what has happened here is very simplistic, so the count of error lines doesn’t map exactly to the number of errors, but it did the job well enough.

private void DisplayErrors(List<string> errorMessages)
{
    if (errorMessages.Count > 0)
    {
        string errorString = errorMessages.Count.ToString() + " lines" + Environment.NewLine;
        foreach (string error in errorMessages)
        {
            errorString += error + Environment.NewLine;
        }
        Assert.Fail(errorString);
    }
}

The reflection starts here. GetAllEventClasses simply finds all of the classes that inherit from my base class, and it specifically excludes the handy DebugEvent class that I used for quick hacks in the main system. GetConstructors was just a useful little call that the actual tests needed.

private IList<Type> GetAllEventClasses()
{
    var q = from t in Assembly.GetAssembly(typeof(LogEventBase)).GetTypes()
            where t.IsClass && t.IsSubclassOf(typeof(LogEventBase)) && !t.Name.Equals("DebugEvent")
            orderby t.Name
            select t;
    return q.ToList(); 
}

private IEnumerable<ConstructorInfo> GetConstructors(Type eventClass)
{
    return from constructor in eventClass.GetConstructors()
           select constructor;
}

With all this code in place, a unit test could then be as simple as:

[TestMethod]
public void EventClassesDontHaveMultipleConstructors()
{
    RunReflectionTest(CheckClassForMultipleConstructors);
}

private void CheckClassForMultipleConstructors(Type eventClass, List<string> errorMessages)
{
    if (GetConstructors(eventClass).Count() > 1)
    {
        errorMessages.Add(eventClass.Name + " has multiple constructors.");
    }
}

The test method just calls to RunReflectionTest passing in an action delegate to the function that will perform the testing on a class by class basis. The testing code simply needs to add to the errorMessages list if it encounters a problem. I’m sure that Uncle Bob isn’t going to be asking me for the rights to put my code in his next book anytime soon, but it certainly made my life a whole lot easier.