Every once in a while, I have conversations with people about what really is TDD. Since I built a certain knowledge on the topic in time not only by using it but also by explaining it to others, I decided to write this article that details my definite view on what TDD is. I hope you’ll find it useful.
This is continuing the article on this topic from the previous Today Software Magazine issue:
If we look at the definition of design above, it’s obvious that TDD is a method to design. We write code to solve a problem and we use tests to define the expected behavior of the solution. When the tests pass, another bit of the problem gets solved. We’ve just created intentionally a piece of code that solves a problem. This is design.
But is it good design? This is an interesting discussion. To answer it, we should look at the design qualities that TDD forces us to build. There are two:
Testability 〝 obviously, since the code is tested
Is this enough to call the result of TDD “good design”? Most likely, the answer is no. It makes complete sense to say that:
The quality of the design depends on the designer’s skills
It’s now time to introduce yet another aspect of software design. Writing the code is not enough; what’s important is to structure the code in a certain way. The least structuring we can do is to write all the code in one huge method. The computer won’t care, but it will affect the qualities of design.
• When doing TDD, a programmer has to make a lot of decisions regarding the code structuring:
use a class, a method, a variable, a member etc.
how to name things
how the classes collaborate
what data types to use
These are all design decisions that contribute to its qualities. Naming a variable “a” will make it less readable than naming it “amountOfMoney”. Adding 10 layers will make the code more difficult to understand than using just two layers. A class with 10 collaborators is more difficult to understand than a class with 3 collaborators.
When do we make these decisions? Always! More concretely:
Upfront. For example, using an MVC web framework means already following constraints for the code structure.
When writing the test: the name of the class under test
When writing the code: the names of private methods, instance members or local variables
When refactoring: extracting a method, extracting a class or just renaming something.
Incremental means that we are designing software one bit at a time. We design a little before starting the cycles, a little while writing the test and a little when refactoring. The act of designing is intentional: we try to improve the qualities of design that are relevant in our context (typically changeability). Incremental also means that the solution grows step by step by slicing the problem into smaller problems. For example, to solve a problem that has as input a list of many numbers, we start from an empty list, then a list with one number and so on.
This process is very similar to another one: general problem solving. The circle just closed: since design means solving problems, and solving problems is best done incrementally, then what better way to call TDD than a way to incrementally design software?
The more knowledgeable readers will probably remember that TDD was often discussed in the context of "emergent design". I think the name “emergent" has a problem: many developers I met tend to think that "emergent design" means design that appears out of nowhere because of a process. Of course, this is incorrect, but it leads to misunderstandings. I favor the term “incremental" as a better description of the process.
And There’s More...
This article focused on the most common use of TDD. Things are a bit more complicated because there is a surprising number of ways of using tests and TDD. Here are some advanced examples, by no means a complete list.
I have used TDD in the past to explore design alternatives: I solve the same problem using different constraints and compare the resulted designs. This is a more complex design method, since exploring alternatives is part of the general design process.
TDD can be used to learn new things. I have successfully used TDD in the past to teach programming languages to people without a programming background.
TDD can be used to explore the problem space as well. I do this by solving a part of the problem and then think how the problem could evolve. For example, if we take Conway’s Game of Life I asked myself: what if the universe changes? What if the time changes? What if the rules change? etc. I often do the same exercise with the business applications i’m building. I sometimes use TDD to explore the potential changes to the features.
In a different category, TDD As If You Meant It is an exercise based on TDD but with additional constraints that delay all the code structuring decisions for as long as possible. It is probably the most incremental approach possible, but it can also lead to code that doesn’t do much even after 1-2 hours. I find it an intriguing exercise but never use it in production.
Your questions and comments are welcome, please don’t hesitate to write at email@example.com.