A series of conversations between Kent Beck, David Heinemeier Hansson, and Martin Fowler on the topic of Test-Driven Development (TDD) and its impact upon software design.
David opened the discussion by raising his three major issues with TDD and Unit Testing: confusion over the definition of TDD and unit testing, test-induced damage through using mocks to drive architecture, and how the red/green/refactor cycle of TDD never worked for him. I commented that to understand where TDD etc came from its useful to understand the history, so Kent explained the origins of TDD by trying things out in Smalltalk, finding that TDD worked well for his personality.
I commented that when we first worked together at C3, we didn’t start using TDD, but ensuring each programming episode delivered code and tests together. Kent said that programmers deserve to feel confident that their code works, TDD is one (not the only) way to reach that. David feels Ruby’s design goal of programmer happiness, and is on board with the notion that you’re not done till you have tests - but doesn’t like TDD as a way to get there. He thinks people have different brains and thus like different techniques and languages, he doesn’t like that TDD gets conflated with the confidence you get from self-testing code.
Kent talked about a recent hackathon at Facebook, about half of which he could use TDD and half wasn’t suitable. In the TDDable code he found he was an enjoyable flow, but found the other part more tricky but still used regression tests and short feedback loops. He has no problem mixing both styles, it’s like playing both classical and jazz, TDD reminds him of how he learned mathematics at school - always needing examples.
David has been in situations where TDD flowed well, but most of his work isn’t like that, his question is what are you willing to sacrifice to get that flow? Many people make bad trade-offs, especially with heavy mocking. Kent thinks it’s about trade-offs, is it worth making intermediate results testable? He uses the example of a compiler where an intermediate parse-tree makes a good test point, and is also a better design. But in response to David’s question about mocks, he says he rarely uses them, he’s concerned that those that do often find refactoring difficult, while he finds testing makes refactoring easier.
I comment that there are two problems with terminology where different things get conflated: first that DHH’s critique of TDD was based on an assumption that you had to use heavy mocking in TDD, which isn’t the case; second that there is a difference between self-testing code and TDD. TDD is one way to achieve self-testing code. David said his reaction was to seeing people describe TDD in a mock-heavy as a moral thing to do and the result was a lot of code that was poorly designed due to its desire to enable isolated unit tests.
I finished by playing time-cop and saying that in the next session we’ll explore how TDD may lead to damage, if that is really damage, and how we can judge if it’s damage.