Test Driven Development: Really? Why?
I am a huge fan of Test Driven Development (TDD). If I spend more that a couple of hours writing a piece of code, in most situations I start to lose track of the details. Exactly what worked exactly how? And if I do this refactoring, have I really covered all of the cases that the old code handled, or did one get lost in the simplification?
This is the reason why I use TDD for any code that has more than one or two execution paths. It may take a bit of extra effort, but I know that I will get that investment back allready later that same day. So it is a no-brainer.
But most developers out there do not use TDD. Some have tried but found it too difficult.
The other day, and acquaintence asked me about TDD. He had been in a project where they had decided to use TDD, but the results had not been great. Here is my proper answer to him.
I had come to see TDD as a means to an end, not a goal in itself. But if TDD is not the goal, then what is?
The job of the software team is to quickly deliver a piece of software that is valuable to the end-user. This is easier if the individual developer can deliver high-quality software changes to a shared codebase often. Often here typically means a few times per day. As a note, high quality here means with few bugs and easy to understand and change in the future. See the Continuous Delivery channel for more nuance.
We are now close to the only software development metrics that are scientifically proven to work, the Dora metrics. There are a few of them, but here i will simplify them to the mantra “Did you release to production yesterday?” We can extend it to a slightly longer “Did you release code to production yesterday, and did that release resolve all bugs you knew about at the time and is the code nice to work with?” This is the goal. Everything else are just tools to help make this a daily reality.
So back to TDD. Use TDD when it will help you maintain the above goals even in a future when noone is around who has any memories of the writing of the original code. This inevitably happens because you can no longer recite every single design consideration from memory (Which in some cases takes a few hours, in other cases a few months), or because the team consists of new people.
A second counter argument against TDD, which hits me as well more often than I like, is that the additional tests and the additional code structure needed to make those tests possible, cause the code to be hard to change.
This usually happens when TDD becomes a metric in itself and unit tests are added because unit tests should be there. The way to avoid this is to take a step back before you code. Your unit tests are called unit tests and not function tests. For TDD to work, the units you test must represent well-defined and stable concepts. One of the best examples is the ever-classic demo of a banking system. Banks have developed concepts like Accounts and Transactions over hundreds of years. These concepts have well-defined meaning that does not change quickly. So make sure that your units correspond to these well-known and stable ideas. That way your tests will test for something that will also be stable. To put it concretely, a Transaction in banking has a given set of properties. These do not depend on if your implementation is just moving values between different parts of memory, or if they are distrivuted across a cluster. Your implementation can change as your system scales. The tests will be mostly the same.
Now, often, we develop new ideas that do not have the legacy of a banking system, but then we should strive to do the same. What, in fact, are the units that make up our system and which we believe to be fairly permanent ideas. Make sure that the tests test those ideas.
In summary:
- Your job as a software developer is to deliver code to production daily, and keep doing that for years on a growing system.
- If your code is such that a change by someone who does not understand the code can cause a bug, then use TDD to develop it to make sure that these bugs to not hinder future you, or others, from causing bugs as side-effects which in turn hinders you from achiaving your main job responsibility.
- When doing TDD, make sure that yhe units you test represent concepts that, to the best of your knowledge, are stable over time will not change rapidly.