I’ve been following Jacob Proffitt’s recent posts, which focus on challenging some of the conventional wisdom around TDD ([1], [2]. [3], cross-posted to TheRuntime.com). (At least, that is my guess at his intention. Alternatively he could just be trying to incite flamewars :)). I’m always happy to have my assumptions challenged, as such challenges can help lead to deeper understanding, so I’ve been careful not to dismiss Jacob’s work and instead have had a good think about it all.
While I disagree with some of his assumptions, logic and conclusions, I definitely agree with the pointy-end of his argument: that plain old unit testing (POUT) without TDD is better than coding without any unit tests. This became especially apparent to me after reading Michael Feather’s Working Effectively with Legacy Code, which helped me realise that once you had code under test, you can refactor and improve the design with not-so-reckless abandon, eventually bringing you to the nice, clean type of design that TDD aims to help you achieve in the first place.
Jacob suggests that the majority of benefits generally attributed to TDD are actually just benefits of POUT and good design principles, and because TDD is more difficult that POUT, the industry should focus on getting people unit testing independently of TDD. By focusing on this distinction between POUT and TDD, I started thinking about his assumption that TDD is more difficult that POUT. What is it about TDD that makes it so hard compared with POUT?
TDD itself is trivially simple: write a test, make it pass, refactor to improve the design. As far as processes go, that’s dead simple. So why do people have trouble with it? Anecdotally, the kinds of problems I normally hear (and have experienced) are things like “How do I test code that relies on a database?”, “How can I test this UI logic?”, “How can I test this unit without setting up loads of dependencies?”. There is a common thread to all these issues: “How do I test [X]?”. Unit testing. POUT. Completely independent of TDD, people have problems designing testable software, and writing unit tests for their designs. Which is understandable: good design is hard.
So is TDD actually the easy part? Are POUT and general software design challenges the real sources of people’s difficulties? Regardless of whether you use TDD or not, maybe the real challenge is unchanged: developing a good, testable design and writing effective unit tests for it.
If there is any truth to this then it does make a good case for TDD. The main aim of TDD is helping developers to get a good design that, as a side effect of the process, can be unit tested. Rather than increasing the barrier to entry for unit testing (or POUTing :)) as Jacob suggests, maybe TDD has the opposite effect by guiding developers to make better (or at least more informed) design choices. That’s not to say that you can’t get a good, unit tested design without TDD of course. It is simply to say that if you are having trouble with TDD, switching to POUT may not help you much. As you will still be facing the same design and testing challenges, but without the feedback provided by TDD, it might actually exacerbate the problem!