BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Programming Concepts
.NET 3.5+

Test-Driven Development

Test-driven development is a process that assists in creating high quality, well designed, loosely coupled and maintainable code that can be refactored with confidence. The process relies on writing unit tests before creating the code that they validate.

TDD Drawbacks

As with all development practices there are drawbacks to test-driven development. They include:

  • The amount of code written is larger than it would be if the tests were not present, due to the code for the tests themselves. The test code is likely to be at least as large as the production code and may be many times larger. This is somewhat offset by the probability that the production code will be smaller, as you should only write tests, and the code to pass them, for functionality that is actually required. You should exclude functionality that might be needed in the future but is not wanted straight away. This is sometimes known as "You Ain't Gonna Need It", or YAGNI.
  • In addition to maintaining your production code you must also update your tests when new functionality is required. This will make some minor changes take longer than they would if new tests were not added. Some changes that must be made to a project can break many tests, all of which must be fixed. You must not give in to the temptation to ignore or delete tests to avoid failures.
  • Some elements of software are difficult or slow to test automatically. Examples are user interfaces, reporting and printing. The tools and techniques available for this type of testing are improving and will likely continue to do so.
  • Rigid adherence to TDD can slow development and be frustrating at times. Sometimes it can be better to break the rules and write some small elements of code and add tests immediately afterwards. You might also decide to create a number of failing tests before writing the production code that makes them pass. I would advise newcomers to TDD to avoid this until they gain more experience with the techniques.

TDD Example

Providing an example of TDD in an article such as this is difficult because you need to see multiple iterations, which involves a lot of code and description. To try to give an impression of the process, the remainder of this article will show a very small example. We will create a single class that represents a bank account. Objects of this type hold a balance and a history of transactions. They also allow deposits and withdrawals.

If you want to follow the process you should create a new solution containing two class libraries. One class library will be used to hold the account class. The second project will hold the tests. This should reference the first project. You should also add a reference to your preferred testing framework. In the code that follows I have used NUnit.

Iteration One

For the first iteration we are starting from a clean solution with no tests or production code. This means that the first test will not compile. We should ideally create a test for the smallest possible amount of code. Let's start by testing that a newly created account has a balance of zero.

The test is as follows:

[Test]
public void NewAccountsHaveZeroBalance()
{
    Account account = new Account();

    Assert.AreEqual(0, account.Balance);
}

To allow the code to compile we need to create the Account class. We want the test to fail so we'll make the Balance property return a fixed value that is not zero. Add the class as shown below:

public class Account
{
    public decimal Balance
    {
        get { return -1M; }
    }
}

Running the test should give a failure message, showing us that the test is able to detect an incorrect starting balance.

Test 'TddExampleTests.AccountTests.NewAccountsHaveZeroBalance' failed: 
  Expected: 0m
  But was:  -1m
    AccountTests.cs(19,0): at TddExampleTests.AccountTests.NewAccountsHaveZeroBalance()

Now we can move into phase two of the iteration, which is making the test pass. This does not mean that we need to fully implement the Balance property. We should aim to write the smallest amount of code possible to make the test "green". This means changing the return value of the property, as follows:

public class Account
{
    public decimal Balance
    {
        get { return 0M; }
    }
}

Finally we move into the refactoring phase. In this case there's no refactoring required.

19 November 2012