Unit testing caveats

There is much controversy regarding test driven development. People do not seem to settle on the time trade-off, the London vs. State school, TDD vs BDD and the list could go on. But before diving into all of these, my first reports from the tranches of unit testing are as follows.

Behavior depending on date and time conditions

Tests should be runnable at any hour. Even if we leave the office at 6PM and everything seems to work properly, having loose DateTime variables in the tests could mean that they will fail during the automatic build that is run every night.

For example, we might want to ensure that our newsletter goes out only on Mondays. In this case, using an ICommand would make sense, because we can verify that our condition has been met before executing the action. A Command accepts a CanExecute Action that could like this one:

Func<Email, bool> canSendEmail = (email) =>
{
    if (email.DateToBeSent.DayOfWeek == DayOfWeek.Monday)
        return true;
    return false;
};

If we’d write our test the same day we’d want our newsletter to go out, indeed everything would work fine. But the next day we’d return to a failing test. A simple way to ensure that this does not happen would be to set the date of the email to a day in time that we know will be a Monday. The same applies to cases where the time of the day or the hour matters.

Wrong:


Email email = new Email();
email.DateToBeSent = DateTime.Now;

sendEmailCommand.Execute(email);
Assert.IsTrue(email.Sent);

Right:


Email email = new Email();
email.DateToBeSent = new DateTime(2014, 06, 16); //This date will be a Monday

sendEmailCommand.Execute(email);
Assert.IsTrue(email.Sent);

Running Execute() does not imply that CanExecute() will also be ran

We’d like to let users interact with our system only if certain conditions are met. For example, we could constrain the user to write the subject of an email before allowing him to sending it.

So our production code could like this:

sendEmailButton.Click += () => {
    if(sendEmailCommand.CanExecute(email))
    {
        // The action is executed only if the subject has been entered
        sendEmailCommand.Execute(email);
    }
}

A normal test for this scenario would be to check if an email without the subject line will be sent. So we’d start by building an email with no subject. Expecting that it should not be sent, we might be tempted to leave out the .CanExecute() check. No surprise, the email will be sent, because the validation of our condition never occurred.

Wrong:

email.Subject = null;
email.SendFinished += (e) => {
    e.Sent = true;
}

sendEmailCommand.Execute(email);

//This Assert will fail, because the command will be executed
Assert.IsFalse(email.Sent);

Right:

email.Subject = null;
email.SendFinished += (e) => {
    e.Sent = true;
}

if(sendEmailCommand.CanExecute(email))
 sendEmailCommand.Execute(email);

Assert.IsFalse(email.Sent);

The order that the tests are run in should not matter

Tests should not depend on each other. They must all pass, no matter in which order we execute them. If in our system we have a component that will be used by two or more test fixtures, we will want to be able to reset it to its initial form. This way, each fixture will be run in a proper environment and won’t not interact with the leftovers of another fixture.

All unit testing frameworks offer this functionality. For example, NUnitLite offers the [SetUpFixture] attribute that decorates a class with two methods: one that runs a setup (before every TestFixture is executed) and one that handles the cleanup (after every TestFixture is executed). Also, in a TextFixture methods can be decorated with the [SetUp] and [TearDown] attributes, to provide the same functionality at fixture level.

Advertisements

One thought on “Unit testing caveats

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s