May 2014Designing for Testability
I've been following along with the TDD is Dead series, and it has made me appreciate how far along I've come these past few years. If you haven't tuned in already, it's a very good listen full of insights at many different levels. It's a battle between ideals and convention. On on side, you have Kent Beck and Martin Fowler: pioneers of TDD and unit testing. On the other, you have DHH, the creator of the Ruby on Rails framework. Each side has a wealth of experience, but look at programming from nearly polar opposite viewpoints.
The main argument DHH (and many others) have against testing is the insane lengths you have to go to isolate code to allow testing. In some cases, you are creating abstractions and layers for the sake of testing. In this sense, the code you are writing is actually worse off without testing.
I want to ignore all of the benefits of testing for a moment here, and focus on design, because I think that's what the discussion really needs to be about. The underlying quesiton is: can you have well-tested code and well-designed code that concisely conveys its intent.
Of course you can, but it's not easy. There is a natural progression of hitting extremes that is part of the learning process. With more experience, you find a good balance. What I see happen, and what turns people off of TDD, is hitting that extreme and stopping. This misses the refactor step of the TDD cycle, and with more experience, the refactorings are more valuable.
Wrapping things in artificial layers and introducing more intermediate results may make testing easier, but it doesn't necessarily make your application any better. More often than not, you can't fix your design issues without fixing things at a higher level. And even more dangerously, if you already think your design is perfect, you have no chance.
As you write new code, you can't be afraid of improving the foundation it is built upon. When you are stuck, sometimes taking only a few steps back isn't enough to find your way. You need a fresh mind and willingness to introduce real change. You need to change code at a higher level and improve how things communicate. Continuing to pass the same messages through a new system might not be enough. You need to improve the message as well. And to me, this is where most people fall short when trying to refactor their application.
If the message remains the same between each layer of your application, the boundaries aren't adding any value. They may help with code isolation, but they are just artificial layers. Without boundaries, you are not gaining trust, and your layers will have cracks.