An interview with James Grenning
The agile community's alphabet SOUP of acronyms (whoops, SOUP stands for "Software of Unknown Pedigree") include XP, TDD, FDD, and many more. TDD, for test-driven development, seems to be getting much more exposure in the embedded arena than most of the others. There's much to like about it, but I find some aspects of TDD unnerving.
TDD inverts the usual paradigm of software engineering. Its adherents advocate writing the tests first, followed by the application code that satisfies the tests. That's appealing; when testing is left to the end of the project, which is invariably running behind schedule, the validation gets dropped or shortchanged. Early testing also ensures the system is being built correctly: errors tend not to require massive restructuring of the system.
Further, TDD is implemented in very short cycles. Write a test, write some code, pass the test, and move on. As in eXtreme Programming the assumption is that code changes are low-risk since the tests are so comprehensive and fast to run.
The tests must run automatically; programmer intervention slows the process down. The suite returns a series of pass/fail indicators.
Kent Beck extolled the virtues of TDD in his 2003 tome Test-Driven Development, by Example.1 I found the book shocking: the examples paint TDD as rampant hacking. Need a function that converts between dollars and Francs? Hard code the conversion rate. That will pass the tests. Then, over the course of far too many iterations, generalize the code to be the sort of stuff that, well, most of us would write from the outset.
Is TDD just hacking?
In my travels I often see companies use "agile" as a cover for a completely undisciplined development process. Agile methods, like all processes, fail in the absence of rigor. So, no, TDD is not about hacking.
But I take issue with some of the ideas behind TDD and think that it needs to be modified for firmware projects. James Grenning (www.renaissancesoftware.net), a signer of the Agile Manifesto and well-known speaker and trainer on agile methods, kindly agreed to be interviewed about TDD.2, 3
Jack: James, I have three reservations about TDD: in my view it deprecates the importance of design (and requirements gathering), and, though the focus on testing is fantastic, it seems test is used to the exclusion of everything else. Finally, I think TDD raises some important business concerns. Let's look at design first.
TDD newbies usually think that test-driven development is all about using tests to crank code, while the experts claim it's primarily about design. Yet the activities are all coding. How can TDD be about design?
James: Jack, thanks for the opportunity to have this dialog. I'll see if I can straighten you out on some of your misunderstandings.
I'll make several points about how TDD is really about design. Let's start with modularity and loose coupling: you cannot unit test a chunk of code if it cannot be isolated and placed in a test harness. To do this you need modularity and loose coupling. TDD leads to it. Testability is confronted daily, keeping modularity and loose coupling (a.k.a, good design) throughout the life of the product.
Then there's doing appropriate up front work: different situations and people require differing amounts of up front design. Before a development effort commences, a team should spend some time exploring design alternatives. This tends not to be formally documented in smaller teams. Also any documentation is usually kept high level, so that it is not mired in the detail that causes a lot of churn. I am not saying you cannot do documentation. I am just saying we don't have to do it up front while things are in flux. If formal documents are needed, we'd create them after getting the ideas to work.
An important part of being successful with TDD is having an architectural vision that guides the team in partitioning the software. The vision also evolves. Like many practicing TDD, I am schooled in the Robert Martin SOLID design principles.4 Essentially, the SOLID principles provide guidance that help produce modular and loosely coupled code. In general, test-driven developers apply these principles to their design vision and daily work. One key idea is that modularity at a small scale supports good architecture.
The code is the design: An important paper, "What Is Software Design?" written by Jack W. Reeves in 1992, made a case that the code is the design, and it is a good case.5 The code is analogous to the blueprints needed to manufacture a bridge or a car, while the makefile contains the assembly instructions. We need to give coding its due respect. The code specifies the behavior of the executable program in all its necessary detail. Take a look at Mr. Reeves' paper (www.developerdotstar.com/mag/articles/reeves_design.html). It may change how you think about code.
We practice continuous design: TDD is a continuous design activity. Design is never done; it is not a phase. As the design evolves, new modules are identified. The initial test cases for the new module are used to explore the interface of the module. The test is the module's first user. This really helps develop clean and easy to use interfaces.
During development, we might find that the design does not cleanly handle some new requirement. In TDD we have a complete set of automated tests. These tests form a safety net that takes a lot of the risk out of refactoring code. In the situation where there are no automated tests, the developer might shoe horn in the new functionality. It's just a small change after all, but it starts the code rot process that has led to many legacy code messes. How does a design rot? One line at a time.
Tests are a design spec: As TDD proceeds, the detailed design requirements are captured in the test cases. This is a very powerful form of documentation. It does not degrade into lies like many other forms of documentation do over time. Once developers become familiar with tests as documentation, they find if much more useful than prose. It also is the first place to look when considering using a module or understanding it so that a change can be made.
Are you aware of code rot radar? TDD acts as an early warning system for design problems. Imagine you can see in the production code where a change is needed for a new feature or bug fix. But you also find that you cannot devise a test that covers this change. This is an early warning of a design problem. The code is telling you, before it is too late, that code rot is starting. TDD gives warnings of functions that are too long, too deeply nested, or are taking on too many responsibilities. If you listen to what the code is saying, code rot can be prevented by making needed design improvements as they become necessary. Like I said a bit ago, the tests form a safety net to reduce the risk of restructuring working code.