You may have seen the recent Kent Beck, David Heinemeier Hansson, Martin Fowler discussion on Is TDD Dead? where David Heinemeier Hansson contests that TDD is “poisoning” good designs. My take on this can be summarised with the below diagram;
Not all Test Driven Code is good and not all Good Code is test driven. But a lot of code (probably most) is neither good or test driven. I contest that to be considered good, code must be both well designed and well tested. Now I could have labelled the above “tested” or even “testable” but I have seen very little code that is written without TDD which gets anywhere near approaching “well tested” or even “testable” no matter how well intentioned the developers. Moreover I believe that by practising TDD you have much better chance of getting to good than by not doing so.
I say this because I have found that people who consistently write good clean code have almost invariably been people who have mastered TDD. Why could this be?
I think the reasons for this is are that in applying TDD you learn that keeping your tests manageable is at least as hard as writing the underlying code. To avoid brittle difficult to understand tests you want tests that test only one thing and you have to balance this with code that is itself manageable and does one thing well. Consequently;
TDD leads you to code that has small interfaces and low cyclomatic complexity.
TDD tells you things about your code; Tests that require a lot of dependant objects to be constructed or mocked are telling you your code has high coupling. Tests that test more than one thing can hint at poor cohesion – classes under test are doing too many things.
Learning about techniques like mocking and stubbing teach you about coding to interfaces rather than concretions.
TDD teaches you about best ways to handle error conditions and exceptions
TDD teaches you about separation of concerns, why for example its beneficial to separate threading from business logic.
As you get practice writing tests you learn that testing multiple layers of abstraction at the same time is hard and intuitively start to understand what layers of abstraction really mean.
TDD also teaches you to understand what it is to be a user of your code and code for the users rather than the implementers benefit.
In becoming a master practitioner of TDD you provide yourself with all these learning opportunities. Some people having done this feel that TDD can occasionally be overkill (Dan North talks about Spike and Stabilise and opportunity cost of TDD here) but it is the very expertise they have gained from TDD that gives them the ability to make this judgement and to proceed in a way that is safe to retrofit tests to. It’s worth pointing out though that most people I know that have have become competent at TDD never really want to code without it (myself included).
So finally if you are wondering if your code is good or bad and haven’t yet mastered TDD I would suggest it probably has considerable room for improvement. Check out Steve Freeman and Nat Pryce’s Growing Object-Oriented Software Guided by Tests and Kent Beck’s Test Driven Development.