Dan Abramov recently tweeted:
TDD paralyzes me. I’m all for writing tests early in the process — especially in library code. But I can’t write them before I *play*. I need to write a shitty draft and play with the behavior to understand what I really want. Then rewrite guided by tests.
Improve your test writing “Flow.”
Sign up to get my test-oriented code snippets.
— Dan (@dan_abramov) January 19, 2019
That's the beginning of a thread, so there's more to it. But it gives me a chance to say to Dan and the many folks who've shared this: You can still grow in test-driven development (TDD). (Heck, I'm still growing.)
But instead of short tweets commenting on other short tweets, I thought I'd reply here. There's room to discuss the gray areas.
TDD Lives in a System of Design
First, let me commend Dan, and my friends who shared his tweet: You've tried test-driven development. More than tried it… you've given it a decent shake. Way to go!
But after the initial rush of success, TDD can become hard. This is where I don't want you to give up. Why is it hard? Because TDD isn't a programming technique that lives in isolation. It's part of a broader ecosystem. To do TDD well, you need:
- To design testable code. Much of this boils down to the SOLID principles, and other principles like them.
- To express requirements in code. Each test lays down a new constraint. There's an art to shaping these constraints so that they triangulate to a solution.
I guess I'm talking about design—the design of production code, and the design of test code. Technically, these aren't part of the red-green-refactor "TDD Waltz.” But practically, your TDD won't get far without design skills. In the earlier days of TDD, there was discussion that TDD ought to stand for test-driven Design.
TDD depends on having fast, executable tests. Most programmers add "writing tests" onto an existing base of "programming," so the "test" part is new to them. This makes the tests stand out. It's easy to think of it as a testing discipline.
But it's not. Test-driven is the adjective. The noun—the root of the matter—is Development. And to do TDD well, we need to get better at Design. (See Your TDD Will Improve as Your Design Sense Improves.)
TDD Lives in a System of Feedback
Much of the Twitter discussion was around the question, "Can't you use TDD but build the wrong thing?" And there were several responses along these lines:
- TDD is good for some code (like libraries), not for others (like UI).
- TDD forces you into linear thinking. So to non-linear thinkers, it feels constraining.
- So TDD is one approach among many. It's not for all programming, nor is it for all programmers.
I'm not shaming anyone, especially my dear friends! That's why I'm replying long-form, instead of in a soulless tweet. Let's turn these into questions:
Is there room for play within TDD? Is there any place for non-linear exploration?
Again, TDD is part of a broader ecosystem. But this time, let's not talk about design so much. (Though if you find TDD hard for UI, it shows there's room to improve the design.) Instead, let's talk about the roots of TDD.
Test-driven development is part of a larger approach called Extreme Programming (XP). XP is a set of values, principles, and rules. "Feedback" shows up in all three. Here's a diagram of the most common feedback loops of XP:
This isn't an exhaustive diagram. The "extreme" part of Extreme Programming says, what if we take a good idea (like feedback) and turn it up all the way? How can we get more feedback, to adjust our course as we go?
So where is play / experimentation / non-linear exploration? The answer is: these are all important forms of feedback. They find their place in a technique called Spike Solutions. I've written about Spike Solutions before; see How to TDD the Unknown with a Spike Solution. But let me summarize what they are, and how to use them.
A Spike Solution is old-fashioned hacking. It's noodling and tweaking. In TDD, it comes before the TDD Waltz. There are important questions we need to answer:
- Are we building the right thing?
- Heck, how do I even build this thing?
Some of this shouldn't even need programming, right? Non-code prototypes are a cheaper way of experimenting with UX and UI. But as iOS developers, we're also faced with many complex libraries that fit together. Quite often, we need to play around in code to get answers.
But here's the kicker: The final step of a Spike Solution is to throw it away.
Why Throw Away Code before Starting TDD?
The goal of a Spike Solution is to answer a question. Part of the discipline of spiking is to get that answer as fast as possible. This means the code can be uuugly. For example, I might sprinkle print() statements around.
But then, throw it away. Copy what you learned into a scratchpad for reference. You can keep the experiment on a temporary branch. But once you learn something, go back to the tip of your main branch and start over. (See my conclusion to Spike Solutions: 7 Techniques You Can Use.)
Why not keep the code? Because it's written without tests. I'm often surprised at the differences that appear when I start over. That's because TDD isn't just writing tests first. It's also emergent design. That is, the design emerges gradually, in response to the forces around it.
Test-after code has shortcomings that impact our agility:
- It's easy to miss a test, leaving some untested code in place. You end up with a safety net that won't catch refactoring errors. And refactoring is essential for adapting old code to handle new circumstances.
- The tests haven't exerted any influence on the design. You end up with designs that remain hard to test. And lack of pressure to write SOLID code leads to brittle code that resists change.
Don't Give Up… Please Share Your Experiences
So, what code do I not TDD? There are still qualities that need human assessment. Is it beautiful? Is it smooth? If we can capture any artifacts from these assessments, we may be able to get a snapshot: "It should look like this." Using these snapshots as Characterization Tests, we can still refactor the code.
But otherwise, doing TDD isn't a question for me. I don't always succeed, but those times become fewer and fewer. When you hit a roadblock to TDD, my encouragement to you is: don't give up. Not right away. Give your wild hacking creativity a chance to explore using the Spike Solution technique. Learn more about design principles. Reach out for help.
When you hit a roadblock to TDD, don't give up. Not right away.