I’ve been enjoying the back and forth regarding the Death of TDD on the interwebs. The intellectual volleying between “legalists” like Robert C. Martin (Uncle Bob) and “pragmatists” like David Heinemeier Hansson (DHH) is nothing short of fascinating. I have a tremendous amount of respect for both these gentlemen for different reasons, and I can see wisdom in each of their views.
DHH argues that an excessive fixation on unit testing has added indirection, abstraction, and conceptual overhead.
Don’t pervert your architecture in order to prematurely optimize for the performance characteristics of the mid-nineties. Embrace the awesome power of modern computers, and revel in the clarity of a code base unharmed by test-induced design damage.
Unit testing indeed isn’t free and, as DHH argues, provides questionable benefit to offset this cost. Yet he’s fixated on test speed being the reason that we code to an interface. I agree that fast integration tests that hit the database are much more practical in the age of SSDs and fast processors. However, speed isn’t the only reason coding to an interface has merit.
We also code to an interface so that:
- We can easily switch out the implementation behind the scenes
- We can agree on the interface and have two separate teams handle each side of the interaction independently and concurrently.
- We can abstract away an ugly DB schema or unreliable third party.
- We can write tests first and use them to help drive the design. Uncle Bob and Kent Beck see this as a core benefit. DHH sees this as test induced design damage.
I again see the wisdom of both sides. TDD has been shown to reduce bugs and improve design. Yet every abstraction has a cost and must be justified. The T in TDD is *more code*. Be pragmatic. The fact is, in many kinds of software, occasional bugs are an acceptable risk. When they occur, fix it and move on.
Uncle Bob is fixated on craftsmanship, perfection, and centralized control. His brand is cleanliness and professionalism. The idea that the era of unit testing could feasibly be replaced by automated integration testing is unsurprisingly viewed as illogical heresy to existing thought leaders in the space.
“It is difficult to get a man to understand something, when his salary depends on his not understanding it.” – Upton Sinclair
Of course, this quote cuts both ways. Some have accused DHH of declaring TDD dead merely because unit testing is hard to do in the very framework he created: Rails.
— Michael Feathers (@mfeathers) April 27, 2014
One important thing to keep in mind: Uncle Bob sells consulting. DHH sells software. It’s a common divide. Software “coaches” like Uncle Bob believe strongly in TDD and software craftsmanship because that’s their business. Software salespeople like Joel Spolsky, Jeff Atwood, and DHH believe in pragmatism and “good enough” because their goal isn’t perfection. It’s profit. So if you want to build the most beautiful, reliable, scalable software, listen to the consultant. If you want to build a profitable product, listen to the salespeople too.
The world is a messy place. Deadlines loom, team skills vary widely, and the impact of bugs varies greatly by industry. Ultimately, we write software to make money and solve problems. Tests are a tool that help us do both. Consider the context to determine which testing style fits for your project.
Uncle Bob is right. Quality matters. Separation of concerns and unit testing help assure the utmost quality, speed, and flexibility.
DHH is right. Sometimes the cost of unit tests exceed their benefit. Some of the benefit of automated testing can be achieved through automated integration testing instead.
My take: Search for the wisdom in both of these viewpoints so you can determine where unit testing has merit on your project.