/ CODING

Unit Testing Tips

I’ve been writing a few unit tests recently. I actually like writing unit tests, I find something very cathartic about seeing those little green ticks show up.  However, one thing that can make writing unit tests a drag is setting up testing data for each test. Well, there are some nice patterns and tools out there to help. Let’s check some out…

AutoFixture

If you just need to generate some data (and don’t care too much about what it contains) you might want to have a look at a nice little library called AutoFixture

AutoFixture is an open source library for .NET designed to minimize the ‘Arrange’ phase of your unit tests in order to maximize maintainability. Its primary goal is to allow developers to focus on what is being tested rather than how to set up the test scenario, by making it easier to create object graphs containing test data.

TestCase Attribute

Sometimes you want to run a number of values through a unit test and you don’t necessarily want to write different unit tests for each of them. The TestCase attribute is very useful here because you can get Nunit to just pass a series of values in:

[TestCase(12, 2, 6)]
[TestCase(12, 4, 3)]
public void DivideTest(int n, int d, int q)
{
    Assert.AreEqual(q, n / d);
}

A really nice thing about TestCase is that each element will appear as a different unit test in your runner, which means if one of the values fails you can instantly see which one failed. I’ve used this pretty extensively in the past, but one area where this starts to make your unit tests hard to understand is when you have a lot of values – pretty soon the number of attributes starts to make it kind of difficult to understand. But fear not, the TestCaseSource attribute is here to save you.

TestCaseSource Attribute

This is something that has been around forever, but I’ve never bothered playing with. Turns out it’s a really nice way of structuring that test data. Basically, you create a class that returns an IEnumerable of your test data.

public class MyTests
{
    [Test, TestCaseSource(typeof(MyDataClass), "TestCases")]
    public int DivideTest(int n, int d)
    {
        return n / d;
    }
}
public class MyDataClass
{
    public static IEnumerable TestCases
    {
        get
        {
            yield return new TestCaseData(12, 3).Returns(4);
            yield return new TestCaseData(12, 2).Returns(6);
            yield return new TestCaseData(12, 4).Returns(3);
        }
    }  
}

Now in my case, I was writing some tests for a simple converter application with some numeric buttons, a backspace key, a decimal point key ‘.’. You know, a numeric keypad. It actually turns out that there are a number of things to consider. For example, what if someone tries to enter two decimal points, or maybe tries to have a bunch of leading zeros. Putting the scenarios into a TestCaseSource made this easy to write and also easy to visualize.

NOTE: my architecture is MVVM, so I made sure I used commands behind all the buttons which means I can test what happens when a user presses a button (effectively testing the UI). For my unit test inputs, I provided a string with the keys pressed and had a little method to execute the appropriate command on the ViewModel.

[Test, TestCaseSource(typeof(KeySequenceTestData), "KeySequenceData")]
public string KeySequence_Commands_GivesCorrectValue (string keySequence) 
{
    var vm = new MainViewModel();
 
    foreach (var key in keySequence)
        ProcessCharacter(vm, key);
 
    return vm.FromValue;
}

private void ProcessCharacter(MainViewModel vm, char character)
{
    switch (character)
    {
        case '<': // user hits the backspace key
            vm.BackSpaceCommand.Execute(null);
            break;
        case '.': // user hits the decimal point 
            vm.DecimalPointCommand.Execute(null);
            break;
        default: // any other key (which in case is a number key)
            vm.NumberCommand.Execute(character.ToString());
            break;
    }
}

public class KeySequenceTestData
{
    public static IEnumerable KeySequenceData
    {
        get
        {
            // simple numeric value tests 
            yield return new TestCaseData("0").Returns("0");
            yield return new TestCaseData("1").Returns("1");
            yield return new TestCaseData("99").Returns("99");
            yield return new TestCaseData("909").Returns("909");
 
            // testing leading and trailing zeros
            yield return new TestCaseData("00").Returns("0");
            yield return new TestCaseData("10").Returns("10");
            yield return new TestCaseData("0.").Returns("0.");
            yield return new TestCaseData(".0").Returns("0.0");
            yield return new TestCaseData("...0").Returns("0.0");
            yield return new TestCaseData(".000").Returns("0.000");
            yield return new TestCaseData("000.").Returns("0.");
            yield return new TestCaseData("000.00").Returns("0.00");
            yield return new TestCaseData("10.00001").Returns("10.00001");
            yield return new TestCaseData("10.10").Returns("10.10");
            yield return new TestCaseData("10..10").Returns("10.10");
            yield return new TestCaseData("10.10.").Returns("10.10");
            yield return new TestCaseData("001.10.").Returns("1.10");
 
            // testing hitting the back key
            yield return new TestCaseData("<").Returns("0");
            yield return new TestCaseData("<.").Returns("0.");
            yield return new TestCaseData("<<").Returns("0");
            yield return new TestCaseData("0<0").Returns("0");
            yield return new TestCaseData("12<").Returns("1");
            yield return new TestCaseData("1<2").Returns("2");
            yield return new TestCaseData("12<<").Returns("0");
            yield return new TestCaseData("0.9<").Returns("0.");
            yield return new TestCaseData("0.<9<").Returns("0");
        }
    }
}

The Builder Pattern for Test Data

The Builder pattern turns out to be a nice pattern for creating test data. I first heard of this usage from Steve Smith when he appeared on .NET Rocks episode 1494. You should take a listen to that episode.

The Basic idea is you create yourself a Builder class for your model object that goes and creates an object for you with some default values and then you have builder methods that allow you to set specific values if that is important for your test. Why not just create the objects with their values straight in your test? Well, you don’t want your tests littered with code to setup objects because it distracts you from the purpose of your test… and what if you change that model object later, you’ve got a thousand different unit tests you need to update.

Here is a trivial example: As I said, I’m working on a simple converter application which has a variety of different Unit objects. So I create myself Builder object which allows me to easily create a Unit to covert.

public class UnitBuilder
{
    private string name = "A Unit";
    private string description = "Description of a unit";
    private UnitCategory category = UnitCategory.Weight;
    private double baseUnitValue = 1;

    public BaseUnit Build()
    {
        return new BaseUnit(name, description, category, baseUnitValue);
    }

    public UnitBuilder WithName(string name)
    {
        this.name = name;
        return this;
    }

    public UnitBuilder WithDescription(string description)
    {
        this.description = description;
        return this;
    }

    public UnitBuilder WithCategory(UnitCategory category)
    {
        this.category = category;
        return this;
    }

    public UnitBuilder WithBaseUnitValue (double baseUnitValue)
    {
        this.baseUnitValue = baseUnitValue;
        return this;
    }

    /// 
    /// Implicit converter object, so we can turn a builder straight into a unit
    ///
    Builder Instance public static implicit operator BaseUnit(UnitBuilder instance) 
    { return instance.Build(); } 
}

Once I have that setup, within my unit test I can use this builder object to create my Unit objects. It will have default values but allows me to override whatever I want, all without having a hard dependency on the constructor for the object. For example,

[Test]
public void SimpleConversion_OneUnit()
{
    BaseUnit cm = new UnitBuilder().WithName("centimeters").WithBaseUnitValue(1);
    BaseUnit meter = new UnitBuilder().WithName("meters").WithBaseUnitValue(100);
    Converter.ConvertUnit(cm, meter, 1).ShouldEqual(0.01);
    Converter.ConvertUnit(meter, cm, 1).ShouldEqual(100);
}

This just makes it really nice to construct tests which are nice a readable without being polluted with too much setup code. You can read a bunch more about this pattern at:

One last thing you may notice, I also used a little library called Should, which makes reading the unit test just a teeny-tiny bit more readable.

public void Should_assertions()
{
   object obj = null;
   obj.ShouldBeNull();

    obj = new object();
    obj.ShouldBeType(typeof(object));
    obj.ShouldEqual(obj);
    obj.ShouldNotBeNull();
    obj.ShouldNotBeSameAs(new object());
    obj.ShouldNotBeType(typeof(string));
    obj.ShouldNotEqual("foo");

    obj = "x";
    obj.ShouldNotBeInRange("y", "z");
    obj.ShouldBeInRange("a", "z");
    obj.ShouldBeSameAs("x");

    "This String".ShouldContain("This");
    "This String".ShouldNotBeEmpty();
    "This String".ShouldNotContain("foobar");

    false.ShouldBeFalse();
    true.ShouldBeTrue();

    var list = new List<>();
    list.ShouldBeEmpty();
    list.ShouldNotContain(new object());

    var item = new object();
    list.Add(item);
    list.ShouldNotBeEmpty();
    list.ShouldContain(item);
}

In Conclusion

These are just some little things that you may consider next time you are writing unit tests. It will make setting up your data a little easier whilst also creating more maintainable and easier to read unit tests.

Till next time, happy coding!

kphillpotts

Kym Phillpotts

Geek, Parent, Human, Xamarin University Instructor. Co-curator of http://weeklyxamarin.com and twitch streaming live coding at http://twitch.tv/kphillpotts

Read More