TDD, Unit tests, Top-down vs Bottom-up.

April 8th, 2005 by Hen

(TDD is Test-Driven Development. Top-Down Development is merely shortened to Top-down here-in).

I’ve long struggled to believe in TDD. After much reflection, my decision was that TDD, while a good idea for bottom-up developers, just didn’t have as much to give top-down developers.

In case I’m using those terms incorrectly; I’m defining a bottom-up developer as someone whose first class in writing an application is the lowest level code, a data structure or a utility; while a top-down developer begins by writing the shell to an application with many stubbed out parts.

For me, top-down development can evolve while bottom-up development really needs to be working to a pre-defined design. Said design was probably top-down, so really the difference is one of good coding being top-down, while one way of creating bad code would be to do bottom-up coding without any initial top-down forethought.

TDD helps people who want to evolve from a bottom-up beginning. It forces them to view their low-level component from the outside before coding, and so the out-of-control problem that bottom-up can lead to is controlled. Top-down already solves this problem, so the part of TDD where it creates better basic units does not apply to the top-down developer.

Before this sounds like too much is being stripped away, I still see two distinct pluses from TDD. While evolving top-down encourages the developer to think about the desired functionality, TDD encourages you to worry about boundary cases, error cases etc before even implementing the code. A bad side of this plus is that the coder is distracted from the development of the major, into the development of a tiny component where they worry about every possible outcome. This seems like a restriction on agility and akin to premature optimisation; though it’s easy to see the argument that catching rare outcomes now is better than catching them at production.

The other plus is that you end up with unit tests as a result, and by virtue of that, code which has been designed to be unit-testable, which while not better code (just better units) at least means you won’t have to redesign at some later stage when you decide to implement unit tests.

I’ve used TDD very happily on two distinct types of occasion. The first is for bug-fixing. Create a test, watch it break, fix the code, watch it pass is a great way to fix bugs. The second is when given a very tight set of inputs and outputs (such as the robots.txt RFC). Writing a test harness and then making the tests pass is the perfect way to develop such a clearly defined problem.

It’s hard to define the negatives to TDD. My main one is that it disrupts the flow of coding; which seems like a very bad thing. Today I will write a Foo application. I begin by writing a unit test for the FooRunner command line class. Then I implement the FooRunner command line class. Its first step will be to call a database to get a list of options; so I begin by writing a test for the database call.

STOP CODING. How will I implement a test on an outside resource? Do I stub out the test call, do I embed a memory-database, spending time figuring out how to create the right state for the call and happy that I can only use the 90% of SQL-99 that is commonly implemented now, or do I run against a dedicate external DB so I can use database-dependent SQL, setting up state there and hoping that nobody else is running a unit test at the same time.

Even if I have decided on exactly which external-resource solution I’ll apply, they all take a fair amount of time.

There’s a 4th solution, which is a variant of the first. Instead of simply stubbing out the test call, I’ll stub out the real call too; which is probably what I’d have done in the original top-down case; and worry about how to implement the code and how to test the code later.

Comments are closed.