Posted August 26, 2011.
[ Tdd Process Patterns ]
http://thoughts.karmazilla.net

The TDD Process Pattern - Again

I have said before that TDD is a process pattern, but I don’t think I was terribly clear on what I meant and how that actually works. To define TDD as a pattern, we need to know what a pattern is. I suppose there are many different interpretations of what “pattern” is suppose to mean, but I draw my definition from the wisdom of Christopher Alexander. According to him, a pattern is a configuration that solves a conflict in a given context — this is the 3 Cs definition. To define TDD, or anything really, as a pattern, we must break it down into these constituent parts and define each. Doing this in the reverse order of the 3 Cs tends to make them easier to read as a whole, though one must be mindful that more than one context can be relevant to a pattern, which shouldn’t be precluded from the start.

The context of TDD is often software development, although it can be any sort of creative act where prolific micro-testing throughout the process is practical and reasonably cheap. It specifically pertains to the creative part, and the people who practice it. That is to say, it is relevant to the software developer, but not to his manager.

The conflict that can arise in this context, and which TDD helps to solve, is a little harder to define. Alexander models the conflict as a field of forces, that are interacting in the given contexts, and which needs to be brought into balance.

One such force is the drive to introduce new features and functionality to the system. This causes changes to happen to the system — changes that introduce complexity and entropy. Another force is our wanting to keep the cost of change low. However, complexity needs to be managed, so it slows us down, and therefore we like to keep the complexity as low as possible. Entropy amplifies the cost of complexity by making it disorderly, an introducing more of the so called accidental complexity. A third force is our aversion to bugs. We want to keep bugs out of our systems; to not introduce them in new code, nor to break existing code that works.

Finally come the configuration part — this is simply the definition of the TDD process as we commonly know it:

  1. Write the simplest failing test you can, that validates a desirable behaviour.
  2. Make the simplest change to the system you can, that makes the test pass.
  3. Simplify the system as a whole, without changing its behaviour.

With this, we now have the parts that makes up the pattern and we can piece them together, to make the complete pattern. The context part is easy enough, but we might have to explain how the configuration resolves the conflict. We can summarise the forces as wanting to change the system, to introduce features rather than bugs, and wanting the changes to be cheap and easy.

When we write a failing test at the beginning of the TDD cycle, we are in essence making the code demand of us that we introduce a certain new behaviour to the product code. So far so good on introducing new features. Then we leave the tests in the code base and keep running when ever we make changes to the system. This way we are prevented from breaking existing behaviour that we have already made to work. So far so good on keeping bugs out1.

When we have a high test coverage, we have a safety net for changes. We can make changes to the system, confident that a test will tell us if we break something. When we can make changes with this kind of confidence, we can make more changes than what is strictly necessary. In other words, we have headroom to make changes that improves the design of the system, without altering its behaviour. This is the refactoring step of TDD. It gives us a designated space for removing any entropy and accidental complexity, that might have sprung into existence when we changed the behaviour of the system. We continuously weed out in our little code base garden, so to speak, and strive to reduce the complexity of the system to its bare essentials. Furthermore, when we continuously test our code, every part of the code at every step of the way, we force it to be testable. Every unit must be testable in isolation, and so it must be possible to isolate each unit. This naturally demands a loosely coupled design. When the parts are properly separated, it also becomes easier for each new functionality to find its proper place in the design, leading to higher cohesion. In essence, we end up with a better design — a testable design.

Thus, the force of wanting to reduce complexity and remove entropy is resolved. While we do often end up with a simpler design, TDD itself does not really drive that part. Rather, the simple design tends to come from the experience of the people who have done TDD for some years. However, if we say that we want a design that is “as simple as can be, but no simpler” then TDD does help us with the “but no simpler” part, by ensuring that our design can provably implement the features we want it to.

With this, we have shown that all of the forces in the context are resolved, to some degree, by the configuration of TDD, and we have made it a pattern proper. The utility of this pattern might be in helping to explain not only what TDD is, but also why people should care. The utility of making it a pattern, the mental exercise of it, is that I had to think a lot about not only the make up of TDD, but also the make up of patterns. My own understanding of both TDD and patterns in general have been made clearer, which is only delightful.

I hope you found this blog post useful.


  1. I am well aware that TDD alone is not enough to keep bugs out of the system. However, it is a significant step of the way, and so I would say that this force is resolved here. Also note that patterns never exist in isolation, but as part of a whole where each part interacts with its neighbours in the system.

blog comments powered by Disqus