Because the power of TDD is operational rather than artifactual, test-before provides several benefits over test-after.
Sometimes folks propose the writing of tests after the code is written. The idea here is that the point of the game is to have the tests, and that it makes no difference whether we do them before we write the code or after.
This idea is flawed: it centers TDD around the artifact — the made test — rather than the triple-balance of made-making-maker.
TDD’s benefit is surely artifactual in part: the made tests have value, and we can easily see that value by comparing code that has no tests to code that has them, regardless of when those tests were written.
But TDD derives enormous benefit not from the made tests — the artifact — but from the making of those tests — the operation, and how that operation affects the maker.
Here are several benefits before-testing (TDD) provides that after-testing either doesn’t provide at all or doesn’t provide as effectively.
First, test-before style requires the geek to have a clue about where they’re headed with the code. It operates somewhat from the outside, specifying an API or a data structure. As such, it is the beginning of detailed code design. Without that beginning, you’re just typing.
Very commonly, a noob will tell me she can’t write the test because she doesn’t know where she’s going yet. And here’s the thing: that’s excellent. It’s critical that you know when you don’t know where you’re going, and you’re doing great to know that.
But at the same time, if you don’t know enough yet to propose a test, you sure as hell don’t know enough yet to push code into a production base. I tell them, great! go play around until you have enough of a clue, then we’ll toss that and write a test.
I still get pushback, to be honest, mostly not from young programmers but from old ones. They want to play around for a while, then push whatever they did during playtime. This is overvaluing one’s typing and undervaluing one’s thinking. Typing is not the bottleneck.
(Noob friends: I realize there’s a lot of nonsense out there that says the only thing one ever does while doing TDD is red-green-refactor. It’s yet another attempt to make unbreakable rules so we don’t have to rely on the judgment that we’re unwilling to help you get. Ignore it.)
Second, test-before is usually far more complete than test-after is. This is, again, operational: because we use the tests to literally drive function in to the code, the fit between test and code is much higher.
After code is written, when I write tests for it, I am writing from a combination of my recall (overrated) and my re-reading (low-quality because re-).
Test-after does some good here, and it may be as strong as the discipline of the tester. But relying on human discipline when we could rely on natural consequence is a mug’s game. Here we see not just the made and the making, but the first peep of attention to the maker.
Third, and here we dive head-first in to the world of the maker, there is the remarkable motivational effect of test-before. When we write a test before, we’re challenging ourselves to pass it. When we pass it, we’re giving ourselves — don’t laugh — a food pellet.
Writing a test after you write the code has another name: "drudgery". As a TDD’er, I guarantee you you’ll have times when you overcode — add function, often unconsciously for which you have no test. And as a TDD’er, I guarantee you’ll hate backfilling tests. We all do.
But the call and response of test-before work offers no such sense. If you’ve looked at my material around RAMPS, you’ll see right away that test-before’s little trick speaks at once to Purpose, to Mastery, and to Rhythm, all vital components of human motivation.
Fourth, again with the maker, test-before provides a rigorous explicit narrow focus, and narrowed focus correlates well with narrowed mental bandwidth.
When we think of ourselves as smart animals, we tend to under-emphasize the animal, particularly the embodiment that being an animal implies. But our bodies, our actual physical construction, have profound limits, and a key one is how much detail we can attend to at one time. (Another important limitation that I have tested on several occasions: the amount of indian food it is possible to eat in one sitting.)
Almost anything that narrows scope is a win for a smart animal, and test-before narrows scope admirably. As with completeness, we could overcome this with discipline, but as with completeness, it’s far easier to sidestep the need for that discipline altogether.
Fifth, we gain from test-before because it provides a kind of operational implementation of modern design principles. This is tricky to explain in theory, but easy to see in practice.
If we take say, SOLID, as our model of modern design principles, this benefit comes from a remarkable correlation: the things you have to do to test-before are broadly the same things you have to do to build a SOLID design.
It turns out that it is remarkably difficult to create non-SOLID code when you’re doing stepwise microtested test-before TDD. Testability seems to map onto good design, in a way we haven’t got at all rigorously defined.
There are easy points: obviously the DIP and the ISP lend themselves to testability. But there are far less worked-out ones, too. To be honest, I don’t care as much why that works as that it does.
Sixth, test-before makes a wonderful collaborative framework. Pairs start their code with the test, and the test is a very rigorous statement about what that code is going to do — before it does it.
The test-code, the setup, action, and assertion that I write clues my pair in right away about where I’m at and where I’m headed, for good or — in my frequent case — for ill. It’s the perfect place to stop and head to the whiteboard.
When partner (or mob) and I are on different tracks, we don’t discover this later, we discover it right at the beginning. This is an enormous timesaver.
Seventh, test-before code is never untestable code and never untested code. If you’re a dewy-eyed innocent you may have never seen untestable code, but it’s all everywhere.
Don’t believe me? Pull some old open-source giant, something with a 100,000 lines of code and ten years of life. You’ll search and search before you find a source file you could test without significant effort. Why? Because it wasn’t written that way.
But no matter, because even if your design is testable, with test-after it may or may not be currently tested. Maintaining test-after discipline in most shops means resisting a tsunami of shipping pressure. Testing is usually the first corner we shave when we’re in trouble.
Can you do it? Sure, it’s just like the other discipline-centric stuff above: if you have an iron will, you could do as well as test-before. But why bother? Nobody shaves a before-test that’s already written. 🙂
So. People ask why we test before instead of testing after. The answer is because of the operational impact of testing before. And the value of TDD is not only in the power of the made — the tests — it’s in the balancing of the made, the making, and the maker.
Have a pleasant and strange Saturday night!