Beginning Unit Testing

Getting started unit testing is a difficult task. I've seen plenty of people learning to unit test and it took some time for me and seems to take some time for everyone before unit testing really clicks. Part of the reason why unit testing is difficult for people to get started with is that the code most developers write is not testable. Yes, not only is it important to test, but it is important for your code to be testable. Most code is so difficult to test that anyone who doesn't know how to unit test will have a great deal of trouble testing anything.

Most testing is difficult because the code you're trying to test has too many dependencies. So we all know that we write code which depends on other code we write. There are a few important dependencies that a lot of people forget about.

Breaking the DateTime Dependency

One seemingly safe piece of code to test is the DateTime class. How is it often used? Well, people use DateTime.Now or DateTime.Today in their code. So what is the big deal? Isn't this easy to test? NO! Testing a method which uses either of those two values is not easy at all. If your application cares about the time of day at all, it is impossible to test it reliably if you're using DateTime.Now.

Two cases for time come into my mind right now: checking for weekends and checking morning, afternoon, and evening. A couple of interesting sites I enjoy change the theme of the site based on the time of day. How could you test that logic? Well, not easily if you're using DateTime directly. To get around this wee need to break the dependency. To do this we need to work with an interface which gives us access to all the logic we used from the DateTime class.

interface ICalendar
{
    DateTime GetCurrentTime();
    DateTime GetCurrentDate();
}

public class Calendar : ICalendar
{
    #region ICalendar Members

    public DateTime GetCurrentTime()
    {
        return DateTime.Now;
    }

    public DateTime GetCurrentDate()
    {
        return DateTime.Today;
    }

    #endregion
}

If you need any more attributes from the DateTime class add the methods for them to the interface and the class.

In order to use these methods in production with our TimeOfDay class, we will need to do a couple of things. As a general rule, it is important to program against an interface. We will start by creating a stub for the class and then testing the stub.

public class TimeOfDay
{
    private ICalendar _calendar;
    public TimeOfDay() : this(new Calendar())
    {
    }

    public TimeOfDay(ICalendar calendar)
    {
        _calendar = calendar;
    }

    public bool IsMorning()
    {
        throw new NotImplementedException();
    }

    public bool IsEvening()
    {
        throw new NotImplementedException();
    }
}

Now we go and write a couple of test methods which we want to have fail since we haven't implemented our methods yet. Then once we've got the tests in place we can fill in the code and have confirmation that we have written it correctly.

[TestClass]
public class TimeOfDayTester
{
    private FakeCalendar _myFakeCalendar;

    public TimeOfDayTester()
    {
        _myFakeCalendar = new FakeCalendar();
    }

    [TestMethod]
    public void OneMinuteBeforeTenAMShouldBeTheMorning()
    {
        _myFakeCalendar.CurrentTime = new DateTime(2001, 1, 1, 9, 59, 00);
        TimeOfDay timeOfDay = new TimeOfDay(_myFakeCalendar);
        Assert.IsTrue(timeOfDay.IsMorning());
    }

    [TestMethod]
    public void NoonShouldNotBeTheMorning()
    {
        _myFakeCalendar.CurrentTime = new DateTime(2001, 1, 1, 12, 00, 00);
        TimeOfDay timeOfDay = new TimeOfDay(_myFakeCalendar);
        Assert.IsFalse(timeOfDay.IsMorning());
    }

    [TestMethod]
    public void EightPMShouldNotBeTheMorning()
    {
        _myFakeCalendar.CurrentTime = new DateTime(2001, 1, 1, 20, 00, 00);
        TimeOfDay timeOfDay = new TimeOfDay(_myFakeCalendar);
        Assert.IsFalse(timeOfDay.IsMorning());
    }

    [TestMethod]
    public void SixPMShouldBeTheEvening()
    {
        _myFakeCalendar.CurrentTime = new DateTime(2001, 1, 1, 18, 00, 00);
        TimeOfDay timeOfDay = new TimeOfDay(_myFakeCalendar);
        Assert.IsTrue(timeOfDay.IsEvening());
    }

    [TestMethod]
    public void NoonShouldNotBeTheEvening()
    {
        _myFakeCalendar.CurrentTime = new DateTime(2001, 1, 1, 12, 00, 00);
        TimeOfDay timeOfDay = new TimeOfDay(_myFakeCalendar);
        Assert.IsFalse(timeOfDay.IsEvening());
    }

    [TestMethod]
    public void EightAMShouldNotBeTheEvening()
    {
        _myFakeCalendar.CurrentTime = new DateTime(2001, 1, 1, 8, 00, 00);
        TimeOfDay timeOfDay = new TimeOfDay(_myFakeCalendar);
        Assert.IsFalse(timeOfDay.IsEvening());
    }
}

Now we can execute the tests and see them fail. This confirms that we haven't made any bad assumptions.

NotImplementedTestFailures

Now we go and write the code which makes the tests pass. Once the tests pass we know that the code works.

public class TimeOfDay
{
    private ICalendar _calendar;
    public TimeOfDay() : this(new Calendar())
    {
    }

    public TimeOfDay(ICalendar calendar)
    {
        _calendar = calendar;
    }

    public bool IsMorning()
    {
        return (_calendar.GetCurrentTime().Hour < 10);
    }

    public bool IsEvening()
    {
        return (_calendar.GetCurrentTime().Hour >= 18);
    }
}

So now when we run our tests again we should see all green passing tests.

PassingTests

If we hadn't kept the DateTime class at arms length we wouldn't have been able to easily test the class. The reason we can't is because we are not able to set the value which will be returned from DateTime.Now. Anytime you use classes which magically give you access to something, they better have an interface. If they do not have an interface then you will need to use a solution like this one to wrap the class inside of another one.

Comments