Tag Archives: Musings

The argument against Test Driven Development

It seems that every time I listen to a software development podcast (which is often), somebody is talking up the benefits of Test Driven Development (TDD).  TDD does indeed have many advantages.  It focuses our efforts by ensuring that the code we write is written for a reason, i.e. to make a test, which has been derived from the requirements specification, pass.  This is in keeping with the YAGNI (You Ain’t Gonna Need It) principle.

TDD also allows a safety net for refactoring, to ensure that, after a refactoring, the code is no more broken than it was before the refactoring.  TDD is also likely to lead to ‘better’ code – more modular, cleaner, with less unwanted dependencies between classes.  And all of these are worthy goals, and perhaps are reasons in themselves for adopting (at least partially) the TDD process.

Where I take exception, however, is that it is somehow implied (and even stated) that TDD cannot help but ensure that the code we write will be ‘correct’.  Wikipedia states that, “Test-driven development offers more that just simple validation of correctness”, which would indicate that TDD does indeed offer validation of correctness.  But it doesn’t.  Let me illustrate the point.

I need a method that will return the square of a given integer.  That seems simple, so I write a unit test.

[Test]
public void TestThatTwoSquaredIsFour()
{
    Assert.IsTrue(Square(2) == 4);
}

Note that this is psuedo-code, but you get the idea.  Okay, let’s have a crack at writing the method to satisfy this test.  Again, Wikipedia gives us some guidance.

The next step is to write some code that will cause the test to pass… It is important that the code written is only designed to pass the test…

So, with this in mind, here is my code.

public int Square(int n)
{
    return 4;
}

This is the simplest code that I can write that will pass the test.  And the test will pass.  Since this is the case, we obviously have not specified the requirements clearly enough (through tests), so we’ll add another test.

[Test]
public void TestThatThreeSquaredIsNine()
{
    Assert.IsTrue(Square(3) == 9);
}

Our code now fails the second test, so let’s ‘fix’ the code in the simplest way possible.

public int Square(int n)
{
    if (n == 2)
        return 4;
    else
        return 9;
}

Run the tests again, and we have a nice green lawn.  But incorrect code.  We can go on adding more and more specific test cases, modify our method under test to pass those tests by using if statements or switch statements, and still have incorrect code.

How about adding a randomization aspect to the test, such as the following?

[Test]
public void TestThatSquareOfRandomNumberIsCorrect()
{
    int n = Random(1000);
    int result = n * n;

    Assert.IsTrue(Square(n) == result);
}

We now have a test that will (probably) force us to write a method which calculates a result rather than selecting from a limited range of options, but there are two problems: first, the test is overly complicated, and second, we have already written the required method inside the test!

So here’s the takeaway: Test Driven Development is a useful methodology which encourages the developer to develop only what is needed, in a testable and maintainable way.  However, it is important to realise that TDD does NOT mean that the produced code is correct in all cases – it is only correct in those cases for which specific tests exist.  As far as TDD is concerned, the code can fail in every other imaginable case.  So keep using your brain as you develop.  Allow TDD to do those things at which it excels, but don’t expect it to have the Midas Touch – or you may very well fail.