I’ve had several conversations lately around the idea that Test Driven Development either requires or enforces good Object Oriented Programming. I’ve always been a bit unclear in my own mind on whether doing TDD leads me to writing better OOP, or whether I try to follow good OOP practices just to take advantage of TDD with a minimum of friction. What I can tell you from painful experience is that TDD will expose badly factored OO code. You might say that doing TDD is like an audit on your OO design. Anytime doing TDD on your code becomes difficult you need to reexamine your class structure.
One of my coworkers likes to say that TDD just leads you to a different solution. I’d argue instead that TDD might just be putting a microscope onto your code to find flaws, and we all dress better and stand up straighter when more eyes are upon us. Besides, I’d say that the code qualities that lead to testability are loose coupling, high cohesion, and a thorough separation of concerns. In other words, just plain, good, clean Object-Oriented code like we should have been writing anyway. One of the nice facts about TDD is that testability quite happily coincides with being good code.
I’ve heard a couple people say that you’ll understand OOP much better when you start trying to do TDD. I can certainly echo this experience. I thought I was an OO guru because I could spout off GoF patterns at will. On my first project with TDD I was abused of this notion in a painful manner. I understood basic layering, but I didn’t know how to truly isolate business and user interface functionality away from the database. I let an overly complex security scheme get in the way of even the simplest business logic testing*. I guess you’d say my first lesson in Inversion of Control/Dependency Injection was learning to push a custom permission set into code during unit tests without having to wire up data in a database or configuration files. Once I made the refactoring to make the permission set decoupled from external dependencies, unit testing started to get easier. The light definitely came on in my head after that.
Writing top down procedural code will not lead to testable code. You simply cannot create code that can be effectively unit tested in the TDD style with procedural code. You can’t isolate functionality to the same degree. Loose coupling is nearly impossible. What’s even more difficult is trying to retrofit unit tests to existing procedural code. A terrible, but common, trap to fall into as a TDD newbie is to forgo writing the tests first when you don’t know how to write the test. Just starting to code thinking you’ll write the automated tests later after you make the code work is a slippery slide into technical debt hell. The resulting code isn’t written with testability in mind and the newbie will often find retrofitting the new code with unit tests laborious or even impossible. The usual outcome is either the tests are too coarse-grained to be easily understood and debugged, or the test coverage is lacking.
You simply must make testability be in the forefront of your mind anytime you’re writing code. For TDD, it’s a better approach to decompose a coding task into the distinct responsibilities and code the constituent classes in order to build the whole from the bottom up. Pick off the first responsibility, code a class or method to do only one responsibility, rinse, and repeat. Only when you have the individual pieces working and unit tested do you glue them together and test the whole. Often times I find that it’s easier to “see” the whole picture after a few of the pieces are built. Little self-contained pieces of code with minimal coupling on other pieces of code will be easier to test.
I think a lot of people struggle with TDD because they’re used to writing procedural code. Learning to effectively decompose code into classes with well defined responsibilities is essential to doing TDD without pulling your hair out. I’m not trying to bash procedural coders. I’m just trying to warn them that they’ll want to work in a more OO fashion to reap all of the benefits of TDD.
Your TDD experience will be much more pleasant and successful with stronger OO skills. You have to be cognizant of coupling and cohesion properties for every single class interaction and method signature to keep the unit tests and code flowing. A TDD Jedi is fully steeped in OO programming and is mindful of the qualities of his OO decisions. Writing willy-nilly procedural code without unit tests is a seductive path to the Dark Side.
Am I being too harsh about procedural coding? Is good OO synonymous with effective TDD, or am I just being an OO bigot? Fire away.
EDIT 11/30/2005: Take a look at Bill Caputo's post at http://www.williamcaputo.com/archives/000245.html for a thoughtful look beyond OO for Agile development.
* Dave, Levi, Vivek – if you’re out there, here’s a belated “I’m sorry”