The Source for Java Technology Collaboration
User: Password:



William C. Wake

William C. Wake's Blog

TDD: Tension, Release, and Generalization

Posted by wwake on March 03, 2004 at 09:46 PM | Comments (4)

Test-driven development uses a tight cycle of "test, code, refactor" to develop software.

Tension and Release
I use the analogy of a stoplight: you start with a green light (all tests passing). Then you write a test; often you're referring to classes or methods that don't exist yet, and get a compiler error (yellow light). You fix this error by writing stubs, and when you run the test it fails (red light). Then you add just enough code to make the tests pass (green light).

Part of this style that may not be obvious is that most of the time you have a "green light": just like driving, you prefer not to spend a lot of time with a yellow or red light.

The most obvious use of this principle is in a technique Kent Beck calls "Fake It ('Til You Make It)": you get rid of the red light by making the code return exactly the answer the test expects. For this test:

public void testLength() {
assertEquals(3, buffer.size("abc"));
}

you might write this code:

public int size(String contents) {
return 3;
}

The first time you see this approach, you're likely to think "that's cheating!" We're supposed to be writing a program to do some complicated thing, and we come back with a trivial answer like that.

When you have tests that don't pass, you should feel tension: you have tests, and you have code, and they don't agree about how the code should work. It's the kind of tension from holding your breath, or trying to thread a needle, or watching a movie where people don't realize the danger they're in (depending how much tension you like, I guess:) It's unstable: what will happen?

The solution is to get out of the unstable state quickly, and release that dramatic tension.

This is important, because most of our tools work best when the system is working:

  • If our system works for the tests we have, but not for some tests we don't yet have, we can add those tests.
  • If other people have made changes, and checked in code with all tests passing, we can integrate their changes and know that if tests fail it's our problem.
  • If our system isn't as well-designed as it could be, we can refactor it. But refactoring is best applied when all tests pass.

Generalization
Once we've done the "fake it" part, we can use generalizationto do the "make it" part. Generalization is a relative of refactoring: where refactoring tries to preserve behavior, generalization tries to, well, generalize it.

In our example, we created a fake response "return 3" for a reason: 3 is the right answer, determined by looking at the length of the string. We can generalize our answer from "return 3" to "return contents.length()".

Generalization goes beyond the tests we already have, to the ones we haven't written yet. (Refactoring, in its pure form, preserves behavior for tests we have written, as well as the ones we could write.) Generalization thus makes a guess about the tests we would write. Sometimes this guess can be wrong, so we may write other tests that we think will pass, just to verify it.

I think of test cases as the shadows of behavior. We write code that we think will project that shadow. The "Fake It" part is like using a cutout to generate the shadow - it's not a true 3-d object, but it's good enough to show the right shadow. Then generalization can be used to "inflate" the code into a full object.


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Cheating
    I don't think that this kind of cheating worth it. It renders the tests useless (at least for the time of development) - they won't show you anymore what parts you have to implement or improve. You'll have the green lights, and you won't be able to use your tests to find where your code needs to be improved. (How do you figure out what parts are fake and what are real? Yeah, you may remember, look at the code, etc. But why not just use your tests to do it more reliably and easily?)

    Posted by: atleta on March 05, 2004 at 05:05 AM

  • Cheating
    Bill seems to see unit tests failing as a source of tension. But a unit test framework should not be seen just as a way of removing errors. The assertions within unit tests indicate what is expected, and the exceptions indicate that either what is expected has not been implemented, or what is expected is not working as intended. There seems to be a confusion in this log between practices of sharing code in team repositories and practices of unit testing. As a rule, code should not be submitted to a repository until all unit tests are passed. But this rule should not be circumvented by faking the tests. Faking the tests inevitably leads to methods not being correctly implemented, because the knowledge is no longer in the code: it is in the developer mind, who remembers (or not) that some method is returning a fake result and still needs to be implemented or generalized. Worse, as soon as the code is shared other developers will assume that the new methods work as advertised, which they will not. A correct way of obeying the rule is to branch the development to a private workspace, and only merge it in the trunk when the new unit tests pass. Co-workers can know about the branch and even use it, but understand that it is work-in-progress.

    Posted by: sngraca on March 05, 2004 at 05:58 AM

  • Cheating
    This kind of thing (returning a fake answer) isn't the ending point - it's the starting point. I'll make a list of test ideas as I go. Some of them will be solved with faked solutions, but the next test would reveal the faking.

    Posted by: wwake on March 05, 2004 at 08:55 PM

  • Cheating
    Re "unit tests failing as a source of tension" - yes - it's a tension for the seconds or minutes until I can make the test pass.

    Re "There seems to be a confusion in this log between practices of sharing code in team repositories and practices of unit testing. As a rule, code should not be submitted to a repository until all unit tests are passed." - I can see I didn't make my point clearly. I was trying to say that you need to make the test pass (release the tension) so you can apply things that work best with all tests passing. (Things like checkin, refactoring, etc.)

    Posted by: wwake on March 05, 2004 at 09:00 PM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds