This post originated from an RSS feed registered with Agile Buzz
by Keith Ray.
Original Post: TDD Explained
Feed Title: MemoRanda
Feed URL: http://homepage.mac.com/1/homepage404ErrorPage.html
Feed Description: Keith Ray's notes to be remembered on agile software development, project management, oo programming, and other topics.
This explanation of Test-Driven Development provided insight to some people when I was doing a presentation on the subject. Perhaps it will help someone else out there. Imagine that you're designing/implementing a class or function to solve a problem without TDD. Even though we tend to think of design/coding as one "lump" (or two), it's actually an incremental and iterative process for most problems. You pick an initial data-structure and/or an algorithm, and see how it handles various aspects of the problem - perhaps by thinking it through or running the code though the debugger. You adjust the data-structure(s) or algorithm(s) until all necessary aspects of the problem are solved. In a traditional disciplined coding process, you then write unit tests for the working code.
In TDD, you do pretty much exactly the same thing, except that you write tests along the way. Each time you consider an aspect of the problem, you write a test to represent that aspect, and then write just enough code (or modify just enough code) to test that aspect, while keeping previous tests passing. When all necessary aspects of the problem have been solved, you end up with a suite of tests and the working code.
In the traditional development style, there's a big psychological barrier against writing tests against code that you just spent an hour or two (or days) getting to work. You made an investment (in code), and now you don't really want your investment to be proven bad, so you don't write a really rigorous suite of tests. This is one reason for the rise of testing departments. From what I've seen over the years, most developers don't write unit tests at all, they rely on the "Quality Assurance" people to do black-box tests; if you're one of those that do write unit tests after coding, good for you! If you don't write automated tests at all, please consider adding TDD to your skill-set.
In Test-Driven Development, each test that you write is for code that hasn't been written yet. There's no investment yet to be proven bad. You think "I'll need to handle this aspect of the problem, so let's represent this as a test" -- the tests are documenting the problem-space. Unlike traditional documentation of requirements, the tests are executable. In the TDD, you write a test, watch it fail (because the code for it to pass doesn't exist yet), then write the code to make the test pass, and watch the test pass, (sometimes) refactor to clean up the code, removing duplication and other code-smells, and then watch the test pass again, showing that your refactoring didn't break anything.
Test-driving the code that solves the problem, independent of user-interface (graphical, web, or otherwise), encourages you to create objects and functions that reflect the problem-domain, particularly if you are trying to do domain-driven-design. Porting Test-driven code is relatively easy, because running the tests can let you know if the solution-part of the ported code is working right, before getting involved in porting the GUI. Contrast that with the code-n-fix approach of writing the GUI first and (often) mixing the GUI code and the solution-code... this mixture makes understanding the problem/solution domain difficult and the mixed-up code is much less portable.
In a "legacy-code" situation, you quite often no longer have access to any design documentation or requirements documentation (other than a user-manual), so you have to reverse-engineer the problem-space and solution-space out of the code. With TDD, you have tests to describe the problem-space, and the solution-space is the code being tested... the rest of the code is the UI, cleanly separated.
In TDD, you don't give up any of the learning you've done about design... you employ a new technique for iterating and testing the design that you're evolving. The hardest part is remembering to keep your (test-code-refactor) steps small: you don't need to solve the whole problem all in one shot.