Stories about Software


What TDD Is and Is Not

In my travels, I’ve heard a lot of people embrace test driven development (TDD) and I’ve heard a lot of people say that it isn’t for them. For the most part, it’s been my experience that people who say it isn’t for them have either never really, actually done it or haven’t done it enough to be good at it. Lest you think I’m just being judgmental, here’s a post of mine from about 3 years ago, before I had truly taken the humbling plunge of forcing myself to follow the red-green-refactor discipline to the letter. My language here is typical of someone who buys into unit testing and even to TDD, but who hasn’t become facile enough with the process to avoid feeling the need to tinker with it and qualify the work. If you cut to the heart of what’s going on in my post there (and I say this hat in hand) it’s less “here’s my own special way of doing TDD that I think is actually superior” and more “I haven’t yet gotten good enough at TDD to use it everywhere and that’s rather frustrating.”


Not long after I wrote that post, I forced myself, on a project, to follow the discipline, to the letter, and I’ve never looked back after the initial pain of floundering during what I used to describe as the “prototyping stage” of my coding. I solved the ‘problem’ of TDD not being compatible with that prototyping by thinking my designs through more carefully up-front, solving only problems that actually exist, and essentially not needing it anymore. TDD wasn’t the problem for me when I wrote that post; the problem was that I wasn’t being efficient in my approach.

It’s been my experience that most of the criticism of TDD comes from people like me, years ago. These are people who either have never actually tried TDD or who have tried it for an amount of time too short to become proficient with it. I can rarely recall someone who became proficient with the practice one day saying, “you know what, enough of this — I don’t see the benefit.” For me this statement is hard to imagine because TDD shortens the feedback loop between implementing something and know if it works, and developers innately crave tight feedback loops.

And since the majority of criticism seems to come from those least familiar with the discipline, there are a lot of misconceptions and straw man arguments, as one might expect. So today, I’d like to offer some clarity by way of defining what TDD is and what it it isn’t.

What TDD Isn’t

I think it’s important that I first discuss what TDD is not. There are a lot of misconceptions that surround the test driven development approach to writing software, and there’s a pretty good chance that you’ve at least been exposed to them, if not slightly misled by them. Not only does this make it hard to understand how the practice works, but it may even lead you or your team to reject it for reasons that aren’t actually valid.

  1. First, TDD is not a replacement for user acceptance testing. Someone who practices it does not believe that it’s a valid substitute for running the application and making sure that it does what the requirements state that it needs to do.
  2. Classic TDD is also not comprehensive automated testing. The by-product of it is mainly unit tests, which are tests for finely grained pieces of code, such as classes and methods. Other kinds of automated tests, such as integration tests and end to end system tests involving databases or other external constructs will be a separate prong of your overall testing strategy.
  3. One thing that I frequently hear as an indictment of TDD is that it doesn’t encourage you to think through your design because you initially do the simplest thing to make tests pass. This is not accurate at all. It simply distributes the planning over the course of your development as you go rather than forcing it all to be done up front.
  4. Test driven development is not and does not claim to be any sort of load testing, concurrency testing, or anything else that you might put under the category of “smoke” or “stress” testing. The tests generated by TDD are not meant to test the behavior of your system under adverse conditions.
  5. Another common misconception is that TDD means that you write all tests for the system before you start writing your code. Frankly, doing so would be wildly impractical, and detractors who cite this impracticality as a reason not to do TDD are arguing against a straw man.
  6. TDD is not a quality assurance strategy. When your software department is contemplating new initiatives and dividing them up according to who will own then, TDD does not belong to the testing group.
  7. Detractors of TDD often point out that it doesn’t address corner cases in application or even class and method logic. That is true, but TDD doesn’t aim to address these. They belong with the other automated tests that will be written later.
  8. And, believe it or not, TDD is not primarily a testing activity. This is probably the hardest for people to wrap their heads around when learning the practice. But if you think about the acronym – test driven development, it is primarily development. The tests driving it are simply a characteristic of the development.

What TDD Is

Having gone over a series of things that TDD is not, hopefully I’ve cleared up some misconceptions and narrowed the field a bit. So let’s take a look at what TDD actually is. I should note here that the flavor of TDD that I’m addressing is “Classic,” triangulation-oriented TDD rather than the behavior driven “London” school of TDD.

  1. As mentioned in the last section, TDD is a development approach. It’s a development approach that happens to produce unit tests as you go, which you can then save for later. I suppose you could discard them and retain some of the benefit of the approach, but that would certainly be a waste since you’re going to want automated tests for your system anyway.
  2. Another facet of the test driven development approach is that you avoid “paralysis by analysis,” a situation in which you are so overwhelmed by the complexity of the problem that you simply stare at the screen or otherwise procrastinate, unsure how to proceed. TDD ensures that you’re constantly solving manageable problems.
  3. TDD produces test cases that cover and address every line of code that you write. With this 100% test coverage, you can change your code fearlessly in response to altered requirements, architectural needs, or other unforeseen circumstances. You’ll never have to look at the system nervously, wondering if you’re breaking things. Your tests will tell you.
  4. Besides allowing you to change code easily, test driven development also guides you toward a flexible design. The reason for this is that TDD forces you to assemble your code with testing seams in it, which are entry points, such as constructor and method arguments, that allow you to take advantage of polymorphism for easier testing. A side effect of this is that your code that’s testable is also easier to configure and mix and match in production.
  5. TDD is also a way to ensure that you’re writing as little code as necessary. Since every change to production code requires a failing test, you have to think through exactly what the system needs before you ever touch it. This prevents speculative coding and throwing in things that you assume you’ll need later, such as property setters you never actually use.

So how is all of this accomplished? Well, TDD is a discipline that follows a specific process and relatively simple process.

  1. As I mentioned briefly, the first step is that you expose some kind of deficiency in the system that you’d like to address. This could be a bug, a missing feature, a new requirement – anything that your codebase does not currently do that you want it to. With the deficiency picked out, you write a test that fails because of the deficiency.
  2. With the failing test in place, you then implement the simplest possible solution in your code to get the failing test to pass, ensuring that all of your other tests also still pass. This makes the system completely functional.
  3. Once the system is completely functional, you look at your quick and dirty fix and see if it needs to be refactored toward better design. If so, you perform the refactoring, ensuring that the tests still pass.

So there you have it: a brief overview of what TDD really is. If you’re interested in more on this subject and you have a Pluralsight subscription, check out my course on continuous testing TDD using a tool called NCrunch, which is all about speeding up your feedback loop during development. Most of this post is from the transcript of that course. If you don’t have a Pluralsight subscription and are interested in a trial, drop me a line and I’ll give you a free week of Pluralsight subscription.

  • I might amend “It simply distributes the planning over the course of your development as you go rather than forcing it all to be done up front” to “…rather than pretending it can all be done up front”, because calling a non-spade a non-spade isn’t enough for me. I also feel the need to provoke everyone who thought it might have been a spade. 😉

    But seriously, this post is beautifully clear and detailed. I expect to save a lot of time by pointing people at it. Thank you!

    • Thanks, I appreciate that. And, for the record, I agree with your take about pretending versus doing. In my efforts previous to TDD, it would be “design it up front” followed later by “see how much, if any, of the original design is left.” It reminds me of the quote “no battle plan survives contact with the enemy.”

      For what it’s worth, I still do a lot of planning and architectural consideration prior to getting down to the business of coding, but I do it in the sense of trying to come up with a pattern/framework/architecture that accounts for me not yet knowing what the details will look like. A construct like a layered architecture for managing dependencies works along these lines. I don’t know what methods will go in which classes, but I know that I’ll want something that shows things to the users and something else that reads information from a persistent store. From there, I drill down into more specifics like “if I had a magic oracle that would provide me with customer objects, what sort of view model would I build?”

  • After reading much about TDD and being skeptical, just found your open mind article. You start by delimiting “is” and “isn’t” and being humble about it, so I enjoyed it much. One of the aspects I truly agree is “paralysis by analysis”. I have faced this many times, when code makes us stop and think indefinitely, til’ procrastination. TDD gives us the possibility of conquering small parts and that’s great for productivity.

    Thank you!

    • Thank you for the kind words — I appreciate the feedback. I started and continue this blog as a way to relate my own experiences and learning, so there’s a lot of “here’s what I got wrong and how I learned from it” in the posts. I certainly don’t have all of the answers, but I hope my experiences are helpful to people.

    • Dave

      if you are skeptical start watching Uncle Bob Videos and Roy Osherove videos as well as 8th light videos on TDD. You need to learn more about what it is, you don’t get it yet. Find an XP shop or someone who works in an XP/TDD shop, even better and learn from them.

      • I agree with you Dave. Part of my comment is more about theory, just because people became blind about what TDD is and isn’t. I know Robert C. Martin and 8th Light, but it would be better yet, if you could list some online references (videos and practical things). This post could be richer. What do you think?

  • Thomas Pierrain

    Thanks for this excellent post! Like Gustavo, I think that one of the most powerful benefit of TDD is its capability to avoid a “paralysis by analysis” when we are overwhelmed by the complexity of the problem (thanks to baby & incremental steps). On the other hand, TDD is really NOT about testing classes. It’s about testing behaviors (see Kent’s original book, but also Ian’s excellent video: TDD where did it all go wrong: http://vimeo.com/68375232). Happy TDD!

    • I think I’ve watched that talk before, but I’m listening to it again while i work today, and enjoying it. The idea that it’s hard to solve the problem and engineer and elegant solution at the same time really resonates with me, and I prefer a divide and conquer approach. This is how I generally do things — get something that works by hook or by crook, and then improve the solution while making sure it continues to work. I do that in life too, not just with code.

      • Thomas Lassanske

        I had a laugh as I imagined things you might “do in life, too…get something that works by hook or by crook, and then improve the solution while making sure it continues to work”. Relationships? Fatherhood? Income?! Did you start the last (and maybe the others) by hooking, or by crooking? 😉

  • Good summary, seems like TDD is just a more formalized top-down approach to problem solving: what problem am I soving? (the test) and how the problem is solved? (the code). A lot of people probably already approach problems like this in an informal way and thus don’t get the extra benefits of TDD mentioned in the post (like being able to refactor, detecting breakages, and expressing intent).

    • Agreed. There is definitely a general logic in it that can be applied to problems in general, beyond programming. I actually was thinking the other day of a post along the lines of “where I do things TDD style in my day to day life” (like fixing the thermostat or something).

    • Your point reminds me Simon Sinek on “How great leaders inspire action” and the “Golden Circle”. Check it out http://www.ted.com/talks/simon_sinek_how_great_leaders_inspire_action

  • Pingback: Agilité | Pearltrees()

  • Pingback: My latest "read it later" links | Aytek's Free Zone()

  • Pingback: The Baeldung Weekly Review 10()

  • Pingback: The Baeldung Weekly Review 11()

  • Pingback: Мартовская лента: лучшее за месяц()

  • Pingback: NCrunch and Continuous Testing: The Must-Have Setup | DaedTech()