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
Connect with Us