The Coad Letter: Test-driven Development, Issue 93, What is Test-driven Development?

By: Coad Letter Test Driven Development Editor

Abstract: In this issue, we look at the practice of Test Driven Development. TDD is a method of ensuring quality software development.

1. What is Test-driven Development?

Test-driven Development (TDD) is a style of development where:

  • you maintain an exhaustive suite of unit tests,
  • no code goes into production unless it has associated tests,
  • you write the tests first,
  • the tests determine what code you need to write.

Let's look at each of these in turn.

1.1 Maintain an exhaustive suite of unit tests

What does it mean to have an exhaustive suite of unit tests? Let's start with the unit test. The classic definition of a unit test is: "a white-box test that is concerned with a small part of implementation-oriented functionality". My view is that if you are writing the tests first, they aren't as white as you may think. I hope to go into more detail on this thought in a later issue. You have unit tests to test that your classes exhibit the proper behaviour. Unit tests are written by the developer who writes the code being tested.

Acceptance tests, by contrast, are black-box, and test that features operate correctly. Acceptance tests are concrete scenarios that exercise the system in a typical fashion. They are independent of the implementation and (in eXtreme Programming anyway) are written (or at least designed) by whoever defines the features (aka the customer).

How do you know if your tests are exhaustive? There are two views on this: the historical view and the current, TDD view.

The historical view

According to [3] you need to test everything that could possibly break. Flipping it around you can say that you don't need to test anything that could not possibly break. This is the historical view, but it is still worth considering.

What sorts of thing could not possibly break? Use your judgement. Simple accessors and mutators don't need to be tested. Note that I said simple. Accessors and mutators are not always simple, sometimes they do some computation such as bounds checking or value truncation and sometimes they implement a completely virtual attribute. Since this is unit testing and you get to see the code, you know these things. If you are writing tests for code whose source you can't look at then you may be better off testing accessors and mutators. Also, if the accessor/mutator becomes more complex at some time you should write a test for it. Constructor Parameter Methods (Ron Jeffries' term from the Smalltalk world) don't need to be tested. As I said, you need to use your judgement here. When you are starting out with TDD you should over-test until you get comfortable with it. As you gain experience, you'll learn where you and your teammates are comfortable drawing the line that divides what could possibly break from what could not.

A Constructor Parameter Method is what Ron Jeffries[3] calls a method that has a parameter for each instance variable and simply assigns its arguments to the corresponding instance variable.

In Java the idea is the same, although the details are slightly different. Ron's Smalltalk example is shown here:

---Sum
Object subclass: #Sum
instance variables: 'name amount'

---Sum class
name: aString amount: aNumber
    ^self new
        setName: aString
        amount: aNumber

---Sum
setName: aString amount: aNumber
    name := aString.
    amount := aNumber

We can do the same thing in Java:

public class Sum {
    private String name;
    private int amount;

    public Sum(String aString, int aNumber) {
        initialize(aString, aNumber);
    }

    protected void initialize(String aString, int aNumber) {
        name = aString;
        amount = aNumber;
    }
}

Generally it's a good practice to have a Constructor Parameter Method for each class, along with the required constructors (which can provide default arguments to the Constructor Parameter Method). Not only is it a convenient way to create instances in your tests, but it also gives an indication that a class is getting too complex. When the parameter list gets too long, it's time to consider refactoring the class... it's probably trying to know too much.

The TDD view

This is much simpler. Using Test-driven Development implies, in theory, that you have an exhaustive test suite. This is because there is no code unless there is a test that requires it in order to pass. You write the test, then (and not until then) write the code that is tested by the test. There should be no code in the system which was not written in response to a test. Hence the test suite is, by definition, exhaustive.

1.2 No code goes into production unless it has associated tests

One of eXtreme Programming's tenets is that a feature does not exist until there is a suite of tests to go with it. The reason for this is that everything in the system has to be testable as part of the safety net that gives you confidence and courage. Confidence that all the code tests clean gives you the courage (not to mention the simple ability) to refactor and integrate. How can you possibly make changes to the code without some way to confidently tell whether you have broken the previous behaviour? How can you integrate if you don't have a suite of tests that will immediately (or at least in a short time) tell you if you have inadvertently broken some other part of the code?

1.3 Write the tests first

Now we're getting eXtreme. What do I mean by write the tests first? I mean that when you have a task to do (i.e. some bit of functionality to implement) you write code that will test that the functionality works as required before you implement the functionality itself.

Furthermore, you write a little bit of test, followed by just enough code to make that test pass, then a bit more test, and a bit more code, test, code, test, code, etc.

1.4 Tests determine what code you need to write

By writing only the code required to pass the latest test, you are putting a limit on the code you will write. You write only enough to pass the test, no more. That means that you do the simplest thing that could possibly work. I think an example is in order. Let's say you are working on a list class. The logical place to start is with the behaviour of an empty list (it makes sense to start with the basis, or simplest, case). So you write the test:

public void testEmptyList() {
    MovieList emptyList = new MovieList();
    assertEqual(0, emptyList.size());
}

To pass this test we need a size() method in the MovieList class:

public int size() {
    return 0;
}

What!?! What's with the return 0? That can't be right. Ah...it is right. It is the simplest thing that could possibly work to pass the test we just wrote. As we write more tests we will likely need to revisit the size() method, generalizing and refactoring, but for now return 0 is all that is required.

When you grasp the significance of this, you will be on your way to mastering TDD.

When you are working this way, you want to work in small increments...sometimes increments that seem ridiculously small. In future issues I'll explore the important and sometimes unexpected benefits and side-effects of testing and coding in tiny increments.

2 Let the computer tell you

How do you know what code you need to write next? Let the computer tell you! Write your tests (and your code for that matter, but I'll save that for another issue...or, if you're in a real hurry feel free to buy a copy of [1]) without worrying about what classes or methods you will need to add. Don't even bother keeping a TODO list. Just write your test, and compile.

If you need to add a class or method the compiler will tell you. It provides a better TODO list than you could, and faster. In the previous example when I compile (using Eclipse) after writing the test (with nothing else written) I get the error

MovieList cannot be resolved or is not a type.

This immediately tells me that I need to create a new MovieList class, so I do:

public class MovieList {
}

I compile again and get another error:

The method size() is undefined for the type MovieList

In response to this I add a stub size() method:

public int size() {
  return 0;
}

Now it will compile. Run the test, and it works. Due to Java requiring a return statement when a return type is defined, we need to combine the steps of creating the method and adding the simplest return statement. I have made a habit of always stubbing methods to return the simplest value possible (i.e. 0, false, or null).

3 Wake's traffic light metaphor

William Wake has captured the above flow with a simple, familiar metaphor: the traffic light.

  • yellow - tests don't compile, classes/methods are missing
  • red - tests compile, but fail
  • green - tests pass

We'll look at this in ore detail in an upcoming issue.

4 In closing

I hope you have enjoyed this first issue of the Test-driven Development Edition of The Coad Letter. I've certainly enjoyed writing it, and have much more information to share with you in future issues.

In the next issue we'll have a look at some reasons why testing, and especially Test-driven Development, is important.

Dave

Bibliography

1
David Astels, Granville Miller, and Miroslav Novak.
A Practical Guide to eXtreme Programming.
The Coad Series. Prentice Hall, 2002.
ISBN 0-13-067482-6.

2
Andrew Hunt and David Thomas.
The Pragmatic Programmer.
Addison Wesley Longman, 2000.
ISBN 0-201-61622-X.

3
Ron Jeffries, Ann Anderson, , and Chet Hendrickson.
Extreme Programming Installed.
The eXtreme Programming Series. Addison Wesley Longman, 2001.
ISBN 0-201-70842-6.

4
Pete McBreen.
Software Craftsmanship.
Addison Wesley Longman, 2002.
ISBN 0-201-73386-2.


The Coad Letter is a service provided by Peter Coad and colleagues.
Feel free to share this issue with others.
©2002 Giraffe Productions


Server Response from: SC4