It was over a year ago that David Heinemeier Hansson made the assertion that “TDD is dead.”
testDrivenDevelopment.Living.ShouldBeFalse();
This resulted in many reactions, including those of Martin Fowler and Kent Beck and led to a series of video discussions involving these three thoughtful influencers. I didn’t watch these videos at the time, but came across them recently and was riveted to the conversation. DHH has such a candid way about him. He has clearly thought through his positions and doesn’t let widely accepted dogma get in the way of expressing his opinion. It is remarkable and admirable.
After DHH wrote the above assertion into a test, these three giants of software development engaged in a conversation that was the implementation, the class under test, to see if the test would pass.
The interaction between Kent Beck and DHH was priceless. Fowler mostly observed and moderated and added some well-timed insight occasionally. If you haven’t already watched these, you absolutely must. If you did watch a year ago, you must watch again.
At 21:20 of this video (below), Kent Beck started (along a line that would lead to) talking about coupling and cohesion.
As Beck started down a path of saying that high cohesion leads to loose coupling, DHH challenged the notion. This, I think gets to the core of his initial thesis in asserting that TDD is dead. He was really talking about how TDD is a practice with a lot of virtue, especially around the idea of confidence and rapid feedback, and it has a potential to reveal insight into the domain and to help flesh out the design, but not without cost. He goes further to argue that dogmatic TDD with rigid adherence to ideas about the performance of the tests can be, and often is, counterproductive. That coupling and cohesion can be changed with partial autonomy and that the relationship between high cohesion and loose coupling can be adversarial makes sense to me. I’ve experienced that as well.
Coupling and cohesion have a relationship, but they are, to a degree, two independent attributes of the source code for whatever unit is of concern (this could be at the level of a method, a class, a library, a process, a microservice, a bounded context, or an entire system). Vladimir Khorikov has a really nice post describing the results of different relative levels of cohesion and coupling. In it he outlines and shows graphically what happens when cohesion is high and coupling low, coupling high and cohesion low, and all 22 permutations of this Cartesian Product. Looking at Vladimir’s illustrations and descriptions, we can all easily recall code we’ve encountered falling into each of these categories.
When DHH is talking about Test-induced design damage and Vladimir about “destructive decoupling,” I hear the same thing. In fact, as DHH points out, much of the decoupling done in many codebases is done in the name of testability. Kent Beck is fond of talking about choosing your tradeoffs and rightly so – this is ultimately the core responsibility of software construction. I recall questioning in the days when I was learning about not only TDD, but unit testing itself, whether things I was doing had reason beyond testability or if I was just doing things for the sake of the tests and changing the structure of my code just to make it testable. It felt wrong, but I dismissed the misgivings with a wave of a hand and a trust that this was making things better.
So many times I’ve taken on faith that something is making things better just because it’s supposed to make things better. Those uneasy feelings often arise for a reason. When I had to make changes because of new requirements or rethinking of domains and found myself going through multiple layers of what was being returned by mocks before I could every get close to dealing with the actual testing of what I wanted to change, I felt pain. Pain is where we finally start to see how things are not as clear as the religion would lead us to believe.
The discussion in the entire series regarding testing the death of TDD ultimately comes down to just this – there are costs associated with every approach we take. Test-Driven Development has undeniable benefits. DHH did us a favor to stem the fervor, though, of considering it to be a silver bullet in our proverbial chamber.
If saying that TDD is dead means that using TDD as a club to beat into submission anyone who isn’t willing to close their eyes and follow leaders into a path of using test coverage as a goal or decoupling everything because of testability, then yes, it’s ok to pronounce death (could it be dead to a degree - maybe it’s only “mostly dead”?) Suspending your skeptical nature and following the one true way is almost always a recipe for failure. Taking what you can from TDD is a good approach. Mocking some dependencies in places where it makes sense and you need to have confidence something works to be able to make changes and using real databases, etc. would make the tests slow is often good advice. Mocking every dependency often costs too much to be a good idea.
Every decision you make in your design and implementation has benefits and it has problems. Every user of The Force has the capacity to turn to the Dark Side. The best thing we can do is to be aware of the choices we have to make, the consequences of the options, and choose consciously.