TDD: Is There Really Any Debate Any Longer?

This post is in response to Dr. Dobb’s editor, Andrew Binstock’s recent editorial about the universality of unit testing. I think we all can agree that it’s universally accepted that unit tests are good. But what about Test Driven Development (TDD)?

Promoting the practice of TDD in the JavaScript world has occupied much of my recent professional activity. I have created testdrivenjs.com to do that, and I’m currently authoring a course for Pluralsight on that very subject. The reason I have done all this work is my very strong belief that TDD as a practice has a large amount of value well beyond what most people understand. In fact, I believe that of all the practices made popular by the agile movement, TDD is the most beneficial practice overall. Here are eight reasons why I believe that TDD should be beyond debate.

1. Better Code

Both unit testing and TDD produce better code than not using tests. To unit test a piece of code, you must decouple it from its dependencies so that they can be easily mocked. That process not only helps you write more loosely couple code, but also encourages you to reduce the number of dependencies a class may have. It will address several code smells like too many parameters in methods and constructors, feature envy (a class that uses another class too much), and too many dependencies on other classes.

Beyond that though, the process of TDD causes an incremental approach to building code. You will follow a red (failed unit test), green (passing unit test), refactor cycle many times while building a class. That opportunity to frequently refactor your code will encourage you to spend more time refactoring it. That means more time renaming identifiers to more intuitive names, more time spent thinking about the readability of your code, more time spent deciding if a given method is too large or too small, more time thinking about if this class should be split up into more than one class.

2. Easier Coding

Because you build a class incrementally in TDD, not in one shot, you only have to worry about one responsibility at a time. You don’t have to keep in your head all the things you want the class to do, all the dependencies it needs to work with, all the inputs and outputs that might break the class, all the conditions you need to check for to make sure that arguments are valid. With TDD you can take that one step at a time. And as you build each case, it often leads you to the next case, and the next one.

Although you can follow the same process without writing your unit tests first, testing after the fact doesn’t inherently encourage you to do this.

3. Solution Triangulation

Because in TDD you incrementally build an algorithm, you can essentially triangulate in to the correct solution. The bowling game kata by the illustrious Uncle Bob (google it) is a fantastic example of this. You can start with an algorithm that solves just a simple case of the problem, then slowly add more cases and refine the solution until it’s 100% correct. That makes it much easier to solve a problem than just trying to get it right the first time in one big shot.

Again test-afterwards doesn’t prevent you from following this process, but it doesn’t encourage it either. TDD practically forces you to follow this process.

4. Coding from the Client’s Viewpoint

With TDD, you write the test first. A test is a client of an object. It calls the class, consumes its return value, and must know if the process was successful. That process makes you think about the object from the client’s point of view, so you will be encouraged to make the object a good abstraction of the responsibilities that the class has. You will make its interface more clean and concise, and the method names will be more explanatory than otherwise.

5. More time thinking up front

We all know that too much up front design is a bad thing, but so is too little. On a micro scale, TDD makes you think more about the design of a particular class. You have to think about what dependencies it needs, what methods it will have, how it will interact with its collaborators, what kinds of values its methods will return. Because you can’t write a test until you have some concept of how you will call a method, and what that method will return, and how you will check its correctness, you will be prone to building better classes that are highly cohesive and follow the Single Responsibility Principle. This point is closely related to the previous one.

Testing afterwards doesn’t encourage this practice. Again you’re free to do it, but as a practice you aren’t encouraged to think about a class because you can just start coding and worry about that stuff as the class gets built.

6. Better Unit Test Coverage

Although unit testing means writing unit tests to cover your code, there are so many variations that code can take. I’m not talking about just the number of branches through code, but all the variations in code. If you take in a string parameter, that string can be null/undefined, empty, or contain a value; it can be overly long, it can contain special characters. Does your code truly handle all these cases? If you test after, the quantity of tests you write will trend lower because you will simply be verifying current behavior. (See the next point)

With test-after, your tests don’t help you decide what code to write, so therefore, variations on tests become less interesting and less a part of the core process

7. Sloth And the Temptation to Skip Unit Tests when Under Pressure

As humans we are lazy. It’s wired into us. If you don’t believe this, read Thinking Fast and Slow by Daniel Kahneman. When doing a task, the part of the task where we are most likely to cut corners is the end. We’re almost finished; we want to be finished; we want to check it off. So if testing is a distinct activity at the end of our current mini-task, we are far more likely to cut corners and write fewer and less comprehensive tests.

Also, when we test afterwards, because it’s at the end of the process, if we are under pressure from our bosses, product managers, or clients, or just our timeline itself, then one of the most tempting corners to cut is the testing. If you’ve already seen that your code is working correctly, then skipping tests for that code is extremely tempting when the pressure to ship is applied. With TDD, that is impossible. You can’t write code without testing it first, so you can’t skip out on the tests regardless of the pressure.

8. No Extra Code

With TDD, the process is to just do the minimum amount of coding to make the test pass. This becomes an art form since you must learn to write appropriate tests that cause you to write sufficient code. But when your tests are all passing, and you’ve cleaned up the code to be as well-factored as you want it, then you’re done. The temptation to write more code that handles some possible future requirement (YAGNI) is diminished since you’re only writing tests to satisfy known requirements of the code.

Testing afterwards doesn’t guide how you write your code, with the exception of making it testable. So it gives you no encouragement to write only the code that you need and no more.

TL;DR

In summary, the difference between TDD and simply writing unit tests is big. Don’t let the commonalities make you think that you’re getting pretty much all the benefits because you write unit tests. If you’re not doing TDD you’re missing out on so much more.

14 thoughts on “TDD: Is There Really Any Debate Any Longer?

  1. Not really, there are cases when TDD doesn’t work, or delivers sub-optimal results.

    TDD is an all or nothing proposition. If even a single member of your team refuses to do it, or is unable to do it right, TDD fails.

    Also, TDD delivers a solution which is very close to perfect, but in most cases you don’t need that – you only need something which is good enough, but delivered yesterday, if possible. Sometimes good enough means rightout shitty. TDD is unreasonable in such cases.

    I think TDD is the academic revenge for the success of OO. Years ago, when OOP was coming about, academia was extremely circumspect – formal and rigorous verification of OO code was difficult and not yet formalized, which is why they kept pushing structured programming for a while. Nevertheless, OOP won over the industry, so academia had to yield. Now they try to bend things their way again, failing to understand the same thing they failed to understand back then: better is the enemy of good enough, and good enough is the target of the industry.

    That’s not to be interpreted like I don’t like TDD, or at least test-first development. But I practise this on my own hobby code, and would never consider it for the average day to day coding jobs at hand, given the cost and time constraints of the projects I’m working on, and the fact that usually the project team isn’t homogenous enough for TDD.

    • In all my experience doing TDD/BDD, I’ve never come across a case where TDD doesn’t work. If a single member of your team refuses to do BDD, you have far bigger issues than whether TDD succeeds or fails. You need to start looking at how your team handles uncooperative members. If The Team has decided it’s going to use TDD, then The Team will decide how it deals with people that refuse to accept how The Team has decided to work. If this is not happening, your team is not an empowered self managing team. Usually TDD will be part of your Definition of Done (DoD). If your Team is producing software that does not meet the DoD, then your team will be failing to deliver potentially releasable increments each Sprint. The Sprint Review and Retrospective will very quickly help the team pinpoint why it is failing. When The Team realise it’s because of one persons decision to ignore the agreed on way of working, this simply won’t be tolerated. Here’s some guidance on running retrospectives if your Team is struggling here: http://blog.binarymist.net/2012/07/28/guidance-on-running-scrum-retrospectives/

      “Also, TDD delivers a solution which is very close to perfect, but in most cases you don’t need that – you only need something which is good enough, but delivered yesterday, if possible. Sometimes good enough means rightout shitty. TDD is unreasonable in such cases.”

      Not really. If you want shitty software, write some shitty unit tests. Good enough usually means quite good. Decide up front what good enough is and agree on what your test coverage will be (this is the Scrum Teams responsibility). I discuss how much is enough here: http://blog.binarymist.net/2012/03/24/how-to-optimise-your-testing-effort/

      “would never consider it for the average day to day coding jobs at hand, given the cost and time constraints of the projects I’m working on, and the fact that usually the project team isn’t homogenous enough for TDD.”

      There’s your problem, the fact that you never consider it.
      If you only use it for hobby code, then you’re loosing the benefits.
      Taking into consideration as you say the cost and time constraints. Can you afford not to do TDD?
      TDDing is considerably cheaper in the long run than not TDDing. http://blog.binarymist.net/2012/12/01/moving-to-tdd/
      How can it be? Well I think Joseph did a pretty good job of explaining why. Scott Ambler’s cost of change diagram also clearly shows how TDD saves money.
      http://www.agilemodeling.com/essays/costOfChange.htm
      I also discuss all your concerns here: http://www.slideshare.net/kimcarter75098/moving-to-tdd-bdd

  2. Pingback: Dew Drop – October 17, 2012 (#1,423) | Alvin Ashcraft's Morning Dew

  3. by far your best resource is Pluralsight’s courses on test first development. There’s 6 hours of content there. I would also highly recommend the book “Growing Object Oriented Software Guided by Tests”. That is a great, comprehensive book on the subject. Lastly for the pure guts of TDD, Kent Beck’s authorative guide “Test Driven Development By Example” is a fantastic book.

  4. Hi,
    I understand and agree all your point but my boss point of view see some data on the table so
    Is there any study showing that what is the ROI for business when you use TDD in development?

  5. “4. Coding from the Client’s Viewpoint

    With TDD, you write the test first. A test is a client of an object. It calls the class, consumes its return value, and must know if the process was successful. That process makes you think about the object from the client’s point of view, so you will be encouraged to make the object a good abstraction of the responsibilities that the class has. You will make its interface more clean and concise, and the method names will be more explanatory than otherwise.”

    For me tghis is the best point and reason to TDD.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s