Re-structuring your unit tests

In my team at work we've been trying out different ways of structuring our unit tests so that they are easier to read for a while now so I thought I'd share it on here. Let's imagine we're testing the following class

public class Account
{
    public decimal Balance { get; private set; }

    public void Deposit(decimal amount)
    {
        Balance += amount;
    }

    public void Withdraw(decimal amount)
    {
        Balance -= amount;
    }
}

I've borrowed the example from the NUnit quick start guide. It is tweaked slightly to make the code a bit nicer but the essentially represents an account where we can deposit or withdraw money. You could imagine that you might see tests like the following to cover it.

[TestFixture]
public class AccountTests
{
    [Test]
    public void DepositExpectBalance100For100()
    {
        var target = new Account();

        target.Deposit(100);

        Assert.That(target.Balance, Is.EqualTo(100));
    }

    [Test]
    public void WithdrawWithFundsUnavailableExpectBalance0For100()
    {
        var target = new Account();

        target.Withdraw(100);

        Assert.That(target.Balance, Is.EqualTo(0));
    }

    [Test]
    public void WithdrawWithFundsAvailableExpectBalance0For100()
    {
        var target = new Account();
        target.Deposit(100);

        target.Withdraw(100);

        Assert.That(target.Balance, Is.EqualTo(0));
    }
}

In order to improve the readability as your tests grow you might separate out pre conditions of the test, such as the account having funds available, into their own methods. You might also move the creation of the class under test up into a setup method as well.

[TestFixture]
public class AccountTests
{
    private Account _target;

    [SetUp]
    public void SetUp()
    {
        _target = new Account();    
    }

    [Test]
    public void DepositExpectBalance100For100()
    {
        _target.Deposit(100);

        Assert.That(_target.Balance, Is.EqualTo(100));
    }

    [Test]
    public void WithdrawWithFundsUnavailableExpectBalance0For100()
    {
        _target.Withdraw(100);

        Assert.That(_target.Balance, Is.EqualTo(0));

    }

    [Test]
    public void WithdrawWithFundsAvailableExpectBalance100For100()
    {
        MakeFundsAvailable();

        _target.Withdraw(100);

        Assert.That(_target.Balance, Is.EqualTo(100));
    }

    private void MakeFundsAvailable()
    {
        _target.Deposit(100);
    }
}

The trouble we found is that you quite quickly end up with a number of different private methods which are only linked only to certain tests. By playing with the structure of the tests though you could re-write the tests above like this

public class AccountTests
{
    private Account _target;

    [SetUp]
    public void SetUp()
    {
        _target = new Account();    
    }

    [TestFixture]
    public class Deposit : AccountTests
    {
        [Test]
        public void ExpectBalance100For100()
        {
            _target.Deposit(100);

            Assert.That(_target.Balance, Is.EqualTo(100));
        }
    }

    [TestFixture]
    public class Withdraw : AccountTests
    {
        [Test]
        public void WithFundsUnavailableExpectBalance0For100()
        {
            _target.Withdraw(100);

            Assert.That(_target.Balance, Is.EqualTo(0));
        }

        [Test]
        public void WithFundsAvailableExpectBalance100For100()
        {
            MakeFundsAvailable();

            _target.Withdraw(100);

            Assert.That(_target.Balance, Is.EqualTo(100));
        }

        private void MakeFundsAvailable()
        {
            _target.Deposit(100);
        }
    }
}

This has the benefit of making the links between the pre condition methods and the method that is being tested much more explicit. It also allows you to set up different pre-requisites depending on which method your testing. For example let's say we were to add a transfer method which relies on another account passed in and therefore set up in your tests for that method. We can now add this to just the SetUp method of the class wrapping the transfer method tests. This makes what is actually required to test the method under test much clearer.

Our Account class now becomes this

public class Account
{
    public decimal Balance { get; private set; }

    public void Deposit(decimal amount)
    {
        Balance += amount;
    }

    public void Withdraw(decimal amount)
    {
        if (Balance - amount > 0)
        {
            Balance -= amount;
        }
    }

    public void Transfer(Account account, decimal amount)
    {
        Withdraw(amount);
        account.Deposit(amount);
    }
}

And our tests would look like this

    [TestFixture]
    public class Transfer : AccountTests
    {
        private Account _secondAccount;

        [SetUp]
        public void TransferSetUp()
        {
            _secondAccount = new Account();
        }

        [Test]
        public void ExpectSecondAccountCreditedFor100()
        {
            _target.Transfer(_secondAccount, 100);

            Assert.That(_secondAccount.Balance, Is.EqualTo(100));
        }

        [Test]
        public void ExpectAccountDebitedFor100()
        {
            _target.Transfer(_secondAccount, 100);

            Assert.That(_target.Balance, Is.EqualTo(0));
        }
    }

It's clear from the code that we only need the second account when we are dealing with the Transfer method. This is obviously an oversimplified example but when you're having to mock a number of different services depending on which method you are calling it can come in quite useful.

This also has the advantage of working quite well with the tools the IDE is supplying. You can see from the class drop down which methods have been tested on the class.

The method drop down then gives you quick information of how many tests there are for that method

We've been experimenting with a few other things as well. Such as grouping together on pre-conditions. For example if we had a number of tests which all required 100 in funds to be available we could create the following inner class

    [TestFixture]
    public class WithdrawWith100Available : AccountTests
    {
        [SetUp]
        public void Make100Available()
        {
            _target.Deposit(100);
        }

        [Test]
        public void ExpectBalance100For100()
        {
            _target.Withdraw(100);

            Assert.That(_target.Balance, Is.EqualTo(100));
        }
    }

We could additionally add a separate inner class for the pre-conditions but you're at the risk of ending up with a very deep test class hierarchy then.

Another alternative is to use the SetUp of the inner class to perform the action. Each test is then very simple and is only asserting what it wants to prove

    [TestFixture]
    public class Withdraw100With100Available : AccountTests
    {
        [SetUp]
        public void Act()
        {
            _target.Deposit(100);

            _target.Withdraw(100);
        }

        [Test]
        public void ExpectBalance100()
        {
            Assert.That(_target.Balance, Is.EqualTo(100));
        }
    }

Credit for the origins of this idea come from a few different sources but probably the original post I've found on it was by Phil Haack although he got the idea from someone else as he mentions. Once we've decided which we're going to go with I'll create a follow up post. I'd be interested to hear any opinions or suggestions though.