The unit tests you produce in TDD represent an investment. You’ll want to ensure they continue to return on value and not turn into a liability.
The concerns:
-
The tests will have their own defects.
-
I’ll have twice as much code to maintain.
-
The tests are handcuffs.
-
Testing everything (via TDD) results in diminishing returns.
I’ll have twice as much code to maintain
It’s true, do TDD and you’ll likely have as much test code as production
code, if not more. However, most sizable non-test-driven systems contain
over twice the code needed. In other words: if C
represents the ideal
amount of production code for a system’s implementation, and TC
represents test code, then 2C ≈ C + TC
. That’s a wash, to me. I’ll
take the version that comes with the tests, thank you.
2C
? Twice the code needed? How’s that? The short answer: In
non-test-driven systems, there’s no high-confidence way to keep code
cruft from building up.
The TDD Bet
I worked with a customer in the Great Lakes area some years ago. They’d deployed a production application prior to my arrival; I was told its initial non-test-driven release was around 180,000 lines of Java code. The team, dedicated to TDD, had slowly but surely added unit tests as they test-drove new features, meanwhile cleaning up what they could in the legacy code. By my arrival, they’d shrunk the codebase to around 70,000 lines; by the time I’d left, they’d shrunk it perhaps another 10,000 lines.
Granted, much of the bloat was a large swath of dead and generated code they’d spotted. But a significant amount was purely unnecessary code.
I’d take the TDD Bet on almost any sizable legacy codebase: Given enough time to add tests and clean things up, it could be reduced to under half its size. A codebase with 100,000 lines of bloated code can transform into a codebase with 50,000 lines of production code and 50,000 more of tests.
Where Does This Bloat Come From?
I’m guilty of producing bloated code on my first pass of implementing some small solution. It’s much like writing (for example, these blog entries): I’ll slam out a few sentences in an attempt to spew what’s in my head onto the computer screen. Then I’ll read what I wrote, trying to put myself into the shoes of another reader: Does it make sense? Is it as punchy as it could be? I usually find numerous opportunities for cleanup!
There’s nothing wrong with slapping out a sloppy paragraph as long as you clean it up later. The same approach works for coding, with the caveat that you don’t want to wait too long–“later” might never come because something new demands your attention. When doing TDD, we ensure we clean up the code every few minutes, using the confidence that a passing test suite gives us.
Without TDD, we don’t do nearly enough of these little bits of clean-up. It’s far too unsafe: The chance of breaking other stuff is way too high. By definition, our non-test-driven code stinks a little bit more every few minutes.
Duplicate code rears its ugly head all the time. Duplication can happen in many forms–small bits of logic appearing dozens of times throughout the codebase, different algorithms to accomplish the same purpose, rote mechanisms where more abstract concepts would suffice.
I watched a developer copy-and-paste an entire 100+ line function in order to introduce a small, 5-line-long code variance. The right thing, of course, would be to use delegation, polymorphism, whatever, as long as it didn’t mean duplicating logic. And the developer knew that, as we might hope. “But I don’t want to change that function. I don’t want to be the one who crashes production because I changed some working code in order to make the system ‘cleaner.'”
Rather than introduce design concepts that minimize redundancy when they must deal with behavior variances, developers make what they think is the least impactful change out of fear. For these programmers, the notion of cleaner provides no immediate, obvious value. We know clean will make their job easier over time, but fear now intimidates more.
So, yes, with TDD, you’ll have as much test code as production code to maintain, but overall the amount of code will not be significantly different.
More importantly, you won’t fear your own code.