A Few Thoughts on TDD and Why we Don’t Always Use it
I’m sure many of you have been following the recent TDD blogosphere altercation with interest. Just in case you you haven’t, here’s a quick catchup:
- TDD is dead. Long Live Testing (David Heinemeier Hansson)
- Monogamous TDD (Uncle Bob)
- TDD is dead? Let’s Kill the Messenger Instead (Gil Zilberfeld)
- When TDD doesn’t work (Uncle Bob)
- Professionalism and TDD in the Future (Rob Ashton)
- The TDD Divide: Everyone is Right (Cory House)
- Test Induced Design Damage? (Uncle Bob)
- Profesionalism and TDD (Reprise) (Uncle Bob)
- TDD When You Can’t Refactor (Paul Hammant)
Read all that? Good. As you can see there are some strong opinions strongly held on this subject.
My own thoughts on the subject are quite simple. TDD is a fantastic idea that turns out to be quite difficult to implement, especially at first. There are several reasons for this.
Slow Progress
The most obvious is that adopting TDD slows you down initially. This is true of any new way of working. If you decide you’re going to stop using the mouse for a day and only use keyboard shortcuts, it’s going to be a frustrating experience and in all likelihood you’ll be tempted to give up within minutes of starting. But if you can push through that pain barrier and accept the initial slow-down, you’ll reap the benefits later.
But there are two other reasons we have for not using TDD that I want to highlight in this post. They are:
- “This code is too simple to bother using TDD”
- “This code is too complicated to use TDD”
Too Simple for TDD
Often early on in the lifetime of a piece of software, you already have in your head a large portion of the design and architecture. And if you’re an experienced developer, you probably have a very good instinct for what that design looks like. You just want to turn it into code as quickly as possible. TDD seems redundant during this rapid prototyping phase, so you skip it.
The trouble is, now when you start evolving that initial design further, a unit test suite would be really useful. But you haven’t got one, and retrofitting unit tests to existing code is slow and painful. So this is the reason why I think a lot of developers who have embraced TDD in theory, end up not actually using it in practice. We lack the self-discipline to start the way we mean to go on.
Too Complicated for TDD
But let’s look at the other side of the equation. Sometimes we really want to use TDD, but are thwarted because it just seems too difficult to do.
I’m glad Uncle Bob admitted in one of his many posts on TDD that it doesn’t fit all types of development. There are many types of coding where TDD isn’t a natural fit, and I have several examples from a series I wrote a while back:
- Test Resistant Code #1 – External Dependencies
- Test Resistant Code #2 – Markup is Code Too
- Test Resistant Code #3 – Algorithms
- Test Resistant Code #4 – Third Party Frameworks
- Test Resistant Code #5 – Threading
What do we do about code like this? Uncle Bob freely admits that much of this type of code requires “fiddling” and is outside the domain of unit tests. And I agree with him. All we need to do is ensure that the business logic isn’t intertwined with this untestable code, in order that it can be written test driven.
Now I don’t know if I’m a special case, but many applications I write are almost exclusively made up of test-resistant code. For example, I write a lot of audio applications, which play or record sounds and display custom waveform visualisations. The amount of code that I can meaningfully unit test is sometimes less than 5% of the total code in the application.
This means that for me to write these applications “professionally”, as a responsible software craftsman, TDD is at best only a small part of the answer. So while I’m happy to keep enthusiastically promoting TDD (and trying to be better disciplined to use it more consistently), I don’t think it’s the final word on writing quality code. I think there’s still plenty of scope for new practices to be discovered and frameworks to be created that help us test those parts of our code which ordinary unit tests just can’t reach.
I have some thoughts on what those might look like, but that’s for another blog post.