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.
— Dan Abramov (@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. (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.
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:
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.)
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:
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:
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.
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:
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 becomes 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.
Where has TDD not worked for you? Please share in the comments below. I welcome your thoughts and experiences.
Programming was fun when I was a kid. But working in Silicon Valley, I saw poor code lead to fear, with real human costs. Looking for ways to make my life better, I learned about Design Patterns, Refactoring, and Test-Driven Development (TDD). Programming became fun again! I've now been doing TDD in Apple environments for 18 years. I'm committed to software crafting as a discipline, hoping we can all reach greater effectiveness and joy.
Please log in again. The login page will open in a new tab. After logging in you can close it and return to this page.