Test-driven development is a funny thing. Many developers, particularly the more experienced ones, hate the idea of writing tests for every small bit of behavior they code. It’s hard enough to get these developers to even try TDD in the first place (“I’m doing just fine, thank you”), and a small bit of exposure to TDD is usually not enough to convince people that its benefits outweigh the discipline it demands. A short demo or kata might impart the mechanics of TDD, but it will only rarely produce the itch to continue.
It takes time and firsthand experience for TDD to get under people’s skin to the point where they enjoy scratching out a test and regret when they cannot. Twenty years ago, we called this becoming “test-infected.”
Back in 1999, I didn’t think writing tests for my code sounded at all enjoyable. Still, I forced myself to play with TDD (then called “test-first design,” or TfD) for a few weeks. I remember initially thinking that it seemed so backward. But I slowly began to feel the gratification it created: I realized that not only did my software always work as expected, but also that I could clean up the bits of code that I wasn’t proud of.
Countless developers have undergone the transformation from skeptic to test-infected over the past twenty or so years. They’ve matured in their practice of TDD, both individually and as a community. Some have advanced to the mastery level, where they understand the occasional times when they can produce code confidently without TDD. But I warrant that all of these test-infected would say that they would never give up the practice.
Get TestRail FREE for 30 days!
We Do TDD
Dave Schinkel created the site We Do TDD to capture the passion behind infected TDD practitioners — as well as to know where he could seek employment with like-minded developers. The site has links to three dozen companies and individuals practicing TDD; more than a third are located outside the United States.
Over two dozen of these parties provided answers to numerous questions about TDD:
- How did you learn TDD?
- What TDD learning resources do you recommend?
- How do you start to test-drive a feature?
- How do you refactor?
- How has TDD helped you design better code?
- How has TDD benefited your customers?
- What are some of the challenges you’ve faced with TDD?
All told, Dave asks more than a couple of dozen questions about team, environment, and development practices. Many respondents supplied detailed answers that provide great insights and enlightenment about TDD.
While many of the respondents provide training and coaching services around TDD, many are shops that create high-quality production software using TDD. You’ll no doubt recognize many of the names of people and companies providing testimonials at We Do TDD, but you’ll also discover some new places that might even make you want to work there.
I found myself drawn to the stories about adoption of TDD, as well as about how TDD has improved design. I’d like to share some of the more interesting thoughts about these topics.
James Grenning, Agile Manifesto signatory and founder of Wingman Software, describes one of the common experiences from the TDD infected: “It was weird at first but became pretty comfortable after a month or two.” Grenning also happens to be the expert about TDD in the embedded development space, where the notion of test-driven code sounds insane to many.
CJ Affiliate’s Stu Penrose affirms that learning TDD “was a gradual process for me. I’m less of a book reader and more of a ‘figure it out by logic and by late nights’ sort of learner. … It didn’t take long to realize that tests were useful, and that writing maintainable tests was an art in its own right.”
Lillie Chilen of Omada Health talks about what helped her internalize TDD over a period of time: “The most common experience I’ve seen of learning to TDD (within a pair programming environment) is the new-to-TDD programmer jumping into writing code, and the more experienced person asking their pair, ‘Should we write a test first?’ after a moment. With this simple question, coworkers kindly guided me toward writing tests before code, and I gradually internalized it.”
Scott Krumrei of Menlo Innovations also learned TDD in a pairing environment, and similarly says that it “took a few months to become fully comfortable with what I should be testing.”
Penrose adds the kind of comment that you rarely hear about a development practice, yet you often hear from TDD practitioners: “Tests became a means of freedom. Having no tests meant bondage to drudgery and risk for my project.” You don’t really ever hear someone talk about, say, story points in this way.
Some folks, such as Nate McKie of WWT Asynchrony Labs, found TDD immediately appealing: “I loved TDD from the beginning but wasn’t very good at it (and neither was my team).” Nate reaffirms that TDD is not something you master in a short time. After fourteen years of practicing TDD, he said, they “still struggle to get better at it every day, but we can’t imagine life without it at this point.”
Colin Jones of 8th Light similarly understood the value of TDD but found it initially challenging: “I kept accidentally breaking code that was far away from the code I was changing. I remember feeling pretty demoralized, and when I heard about unit testing, I immediately saw the benefits but didn’t really see how to add meaningful tests to my project. … Then when I joined 8th Light, learning TDD well was part of the deal.”
I love honest, enthusiastic comments like the above. But even better is a testimonial out of the mouth of a true skeptic — someone who was dead set against TDD when they first encountered it. These become the favorite stories of TDD coaches like myself.
I once coached a young but sharp developer named Tim. I remember him laughing dismissively at the idea of TDD, but a few years after I’d worked with him, Tim sent me a kind, unsolicited note about how TDD had changed his career.
Erik Dietrich of DaedTech relates an even more radical transformation. He started doing TDD in order to “speak from experience and authority about how it wasn’t actually that great.” He’d done a little TDD and found value in its byproduct of unit tests, but not so much the test-first aspect of it. He decided to try to tank TDD upon starting his blog, and he resolved to do 100 percent test-driven development in order to ensure his critique was meaningful and credible.
“It was hard, and weird, and new, but I followed the discipline and daresay I started to be decent at it. And, as I did, an unexpected thing happened. I went from finding it stupid and awkward to being kinda sorta okay with it. By halfway through the project, I didn’t hate it.
“Toward the end of the project, though, I had an epiphany. I spent the better part of a day writing code for it without ever actually running the application. I wrote tests, got them to pass, refactored, and kept going in a nice rhythm. After about six hours, when I was done with the feature I’d set out to do, I finally ran the app. Everything worked perfectly the first time. I was hooked.”
Why Would You Want to Be Infected?
Words like “hooked” and “infected” appear in more negative contexts than positive. I’ve portrayed TDD before as a “habit,” another word that has both good and bad connotations. A good habit can even be deemed as a nuisance or chore even though it provides a benefit — brushing your teeth, for example. In other words, most people are willing to accept the nuisance if they understand the value.
“Hooked” and “infected” sound more dangerous than “habit,” however. People get hooked on things like narcotics, and infections can literally drive you mad. But they’re also used in a passionate sense.
The external perception of TDD is that it is a technique to produce unit tests. Yet the infected developers will tell you, sometimes harshly, that “TDD isn’t about unit testing.”
Wait, something called “test-driven development” isn’t predominantly about testing?
Yes, TDD produces things most of us call unit tests — small tests that get executed by a unit-testing tool. Some people call these microtests, and others even call them behavior-driven development tests (which is confusing, because behavior-driven development, or BDD, is typically used when talking about higher-level tests that demonstrate end-user goals, not small unit behaviors).
BDD probably would have been a better name for TDD. With each new small bit of behavior we need in the system, we first describe that behavior by writing a few lines of code to accomplish the following:
- Create or emulate the desired context in the underlying system
- Interact with the system to effect the behavior desired
- Verify that the expected behavior actually occurred by asking questions of the system
It just so happens that these three steps produce what most people call a unit test. I used the term “byproduct” earlier when relating Erik’s story: We focus on describing behaviors, and we produce unit tests as a byproduct of that focus.
So what? Well, we’re bumping up the ROI list for TDD. Not only do we describe what the bits of the system do, we also end up with a unit test suite that can demonstrate regressions. Further, we get a comprehensive regression suite because we’ve described every intended behavior.
So what? Well, that comprehensive coverage provides us with an immensely powerful ability to incrementally rework the design on a continual basis. We have countless opportunities to make small tweaks to the system, keep its quality from degrading, make it understandable to people who must change it, and best accommodate those future changes.
In other words, TDD accommodates the notion of continual design. We don’t have to pretend we can produce the perfect model for design in advance of development. We don’t have to settle for systems that continually degrade due to fear of changing stuff that kind of works.
Let’s hear from the TDD-infected about how TDD has impacted their outlook on design.
TDD and Design
McKie reaffirms my contentions: “TDD helps us think about the problem before we code. Needing to write a test first means that we have to consider the inputs and outputs before we even get to solving the problem. This creates better interfaces and code that is easier to implement and read by the next developer that comes along. Having tests also provides a usage example for anyone who wants to use the code later on.”
Cody Duval of Stride provides a few caveats: “(A)n outside-in approach to TDD can also be useful in thinking about how the largest objects in our code interact. It also aids in guiding future design choices by ensuring changes in one part of the system aren’t breaking others.
“On the other hand, a heavily mocked TDD style can lock in design choices that make future changes more difficult. We’ve also seen test-driven code that leads to needless abstraction, dependency injection and complication.”
“In general, I think careful TDD gives us feedback on our design as we go. It can also be useful to identify design smells (e.g., too many assertions, too many mocks). But it’s dangerous to assume that TDD automatically leads to better design.”
Indeed, TDD doesn’t magically solve the design problem, but it does provide you with constant opportunities to think about the design and address deficiencies in it.
Jones provides a similarly tempered perspective on TDD, along with a perceptive insight about how TDD helps drive a more flexible and convenient interface: “TDD helps here because we are literally consuming our APIs twice: once in the tests we write, and again when we use the code in production. It’s not foolproof and I’ve seen plenty of bad code written both with and without TDD, but it can be a really useful tool.”
I’ve written extensively on the benefits of TDD, and yet I still encounter new ones from time to time. Dietrich adds this benefit to the list:
“With the TDD approach, I got away from building and running the application a lot, in favor of letting my IDE continuous testing tool show me red and green, and iterating in a tight feedback loop. As a result, I stay more focused on the code for longer periods of time, in a state of ‘flow.’ This is important because it minimizes that gap where you think of something you want to do but can’t remember it a few minutes later. I stay locked in and fewer things slip through the cracks.”
Dietrich also talks about the synergy between testability and “good design” concepts, something Michael Feathers has written about:
“When you practice TDD, you make your code testable, ipso facto. And when your code is testable, you see the properties of good code: small and cohesive methods, good use of polymorphism and abstraction seams, well designed interfaces and APIs, etc.”
TDD isn’t without challenges, and it’s certainly not a panacea. Make sure you read the respondents’ answers to the question “What are some of the challenges the team has faced with TDD?”
What I love most about the We Do TDD site: I’d have thought that so many years of practicing a simple concept would reveal all there is to know about TDD. Yet I still continually find great nuggets of wisdom when I have the opportunity to read about the experiences of other infected developers.
Article written by Jeff Langr. Jeff has spent more than half a 35-year career successfully building and delivering software using agile methods and techniques. He’s also helped countless other development teams do the same by coaching and training through his company, Langr Software Solutions, Inc.
In addition to being a contributor to Uncle Bob’s book Clean Code, Jeff is the author of five books on software development:
- Modern C++ Programming With Test-Driven Development
- Pragmatic Unit Testing
- Agile in a Flash (with Tim Ottinger)
- Agile Java
- Essential Java Style
- He is also on the technical advisory board for the Pragmatic Bookshelf.
Jeff resides in Colorado Springs, Colorado, US.
Test Automation – Anywhere, Anytime