Steve Berczuk speaks with Dan Wellman about the agile practice of test-driven development (TDD), including prerequisites to instituting TDD, the technical and cultural challenges to using it, and some ideal (and not-so-ideal) contexts for it.
Dan Wellman is director of technology at Cyrus Innovation, a consultancy that helps startups and enterprises craft custom software and implement process improvements. Dan knows a lot about the technical side of agile. He also understands how agile adoption involves both technical and cultural issues. I had the chance to ask Dan some questions about one of the agile practices that challenges developers' habits the most: test-driven development (TDD).
Steve Berczuk: What is TDD?
Dan Wellman: TDD is a core practice of Extreme Programming (XP). In a nutshell, TDD is the practice of writing automated tests for the production code before writing the code and using feedback from the tests to guide the program’s design. This includes the red-green-refactor cycle, which goes something like this: Write a failing test, see it fail (the test-runner progress bar will turn red), write just enough code to make the test pass (the bar will turn green), refactor the production and test code to improve the design, and repeat.
Steve Berczuk: Why is TDD useful in your work?
Dan Wellman: TDD is a tool that helps me improve the quality of my code and design. Because I write the tests before I write the production code, I'm likely to end up with a comprehensive test suite. It's a lot harder to skip the "make sure there's a test for this code" step if I write the test first! TDD offers another source of feedback on the design of my code. If the code is hard to test, it may indicate that there are some coupling or other design problems. By writing those tests first, I get a sense of these problems before (or as) the production code is written.
Steve Berczuk: Writing tests before you have code is a hard concept for some. Can you get some of the same benefits from having a discipline of testing before you commit changes, rather than strictly before writing code?
Dan Wellman: I suspect you can get some of the same benefits. It comes down to the values for me. I value having a suite of tests that I trust will tell me when I've introduced a mistake, and I value having a design that is easy to change. If I have those, then I don’t mind how the code was created. Following TDD makes it easier for me personally to satisfy my values, so I practice it.
I really like J. B. Rainsberger’s explanation of how TDD works. He mentions a distinction in terminology—"test-first" (writing the test code first, then the production code) versus "test-driven" (writing the tests first and using the feedback from the tests to change the design).
There's another variation—I’ve seen it informally called "test-last"—where the rhythm is: Write the production code first and the tests afterwards. I consider that different from TDD, as TDD implies that the tests are written first and the design is shaped as a result of the test and code cycle. Test-last can still deliver a full suite of tests for an application, though, and that provides safety and confidence when working with code. I find it harder to actually write those tests when trying to do test-last.
Steve Berczuk: Do you introduce TDD in all your coaching engagements?
Dan Wellman: For our coaching engagements, generally yes. In many cases, clients hire Cyrus Innovation because they want to learn how to apply the technical practices from XP to their agile project, which includes TDD as well as other practices like refactoring, simple design, continuous integration, and collective code ownership. So, in those cases, we do. Sometimes clients are already practicing TDD, and we help them improve their technique.
Steve Berczuk: Are there some contexts where TDD doesn't make sense?
Dan Wellman: Recently, we've started working with new startups (as opposed to enterprises or "established" startups), and we sometimes think differently about which practices we use and how we apply them. This is a subject of vigorous and healthy debate at our company. For example, if a new startup has six weeks to prove itself as a worthy investment to funders, a rigorous suite of tests and a highly flexible design might not be the most important use of developer time and money. Sometimes that means not writing tests at all. Sometimes that means dialing back the scope of the tests—for example, writing high-level acceptance tests but not writing as many unit tests, or vice-versa.
Steve Berczuk: What are the prerequisites to a team’s starting to use TDD?
Dan Wellman: I'd say interest, motivation, some knowledge about how TDD works, and some slack to try, fail, and learn. TDD, like any new skill, takes some time to practice and learn. And, I'd say after several years of practicing TDD that I'm still learning! Generally, that means it takes longer for a feature to go from "started coding" to "finished coding," though typically those features have fewer programmer mistakes in them as opposed to features implemented without any tests at all.
Steve Berczuk: Are there domains or aspects of a project where TDD makes more or less sense?
Dan Wellman: When I'm not sure how to design or implement a feature, I start an experiment without worrying about tests and with the aim of getting some fast feedback: "Is this even possible?" "What happens when I try this?" Once I’ve settled on what the design would look like, I revert the code and start applying the TDD process from scratch.
I don't typically test drive UI layout or look-and-feel changes. I may test that certain elements are present (e.g., "If I've got the editing permission, I should see the save button"), but I tend to find more value in testing the application’s behavior and business logic.
Steve Berczuk: What are the major technical challenges to adopting TDD?
Dan Wellman: Many teams start trying to practice TDD on an existing project that was built without any automated tests. That seems to me to be one of the hardest tasks for a programer to do. It means learning TDD and trying to apply it in a codebase that might be hard to test. In those cases, I've seen it pay off to be persistent and creative. And, reading Michael Feathers' Working Effectively with Legacy Code helped me immensely when I stared doing this. If you want to start practicing TDD and you're swimming in a legacy code base, I can't recommend this book highly enough.
Another challenge is if the project interacts with a third-party framework or API that is hard to test. What I've seen work in those cases is introducing an adapter layer that separates the core application's code from the third-party code. Alistair Cockburn describes a design pattern that uses this technique called the “ports and adapters” style. Steve Freeman and Nat Pryce describe how to build a system using this technique in their book Growing Object-Oriented Software, Guided by Tests.
Steve Berczuk: How does TDD change the way that you think about your code and your design?
Dan Wellman: I've found that practicing TDD means that I have to think about the design a lot, and that can be hard! When I first started practicing TDD, I wrote tests before my production code, but I didn't notice the design smells in the production or the test code. That led to a test suite that was overspecified. Changes to the production code would cause tests to fail even when there wasn’t actually a mistake! That makes development slower, not faster.
As I've practiced TDD more, I've learned to slow down a bit and think about the design of my code—in the small at the object level, and in the large at the system scale.
Steve Berczuk: What are the major cultural challenges to adopting TDD?
Dan Wellman: Generally, if some developers on a team practice TDD and others don’t, then the team may not get all the benefits of TDD. I’ve seen the benefits pay out when everyone on a team is following the same practices, encouraging each other, and learning.
If the project sponsors won't allow slack in the schedule or scope for the team to learn how to practice TDD, then that's a recipe for a problem. Velocity usually drops while the development team learns to apply TDD, though hopefully quality (and predictability) is improving. If the team starts experiencing time pressure, then there needs to be safety and space to keep practicing TDD, or it tends to be one of the first things jettisoned.
Steve Berczuk: How to you encourage skeptics to try TDD?
Dan Wellman: Well, if they absolutely aren't willing to try it, I won't try to coerce them into doing it. Instead, I'll try to find folks who are interested in trying it and work with them.
If they are willing to talk with me about why they are skeptical, I'll ask them what challenges they’re having with their development process. If I suspect TDD might help them with their challenges, I'll tell them, and I'll be honest that it might or might not help them and it might be hard. If they're interested, I'll ask them to try it with me for a little while. I'll choose something to work on that I think could address some of the problems they’ve mentioned.
Steve Berczuk: How does using or not using TDD affect other aspects of the software development cycle?
Dan Wellman: I work on teams that prefer to follow the XP practices, which means following what James Shore and Shane Warden call "simultaneous phases." That means that many phases of the software development lifecycle—requirements, design, coding, and testing—are happening at once. TDD is one of many practices that make that process possible.
I've worked at other jobs that didn't use an agile process, but I practiced TDD on some of the code I wrote for those projects. When I did use TDD, I felt more confident in the code I delivered, and I was less worried about making a mistake that caused an emergency.
Steve Berczuk: Thanks for helping us understand the various aspects of TDD. I enjoyed our conversation. It seems like there is definitely more to TDD than simply writing tests. Any last words for someone who wants to get started with TDD?
Dan Wellman: Thank you, I enjoyed it, too! The good news is that there are now many ways to learn about TDD. There are plenty of great articles, books, screencasts, and example projects available. I'd encourage someone new to practice, be persistent, and take pride in each new accomplishment or lesson learned.