Mar 29 2013

The Unreasonable Effectiveness of TDD

Brian quote

In 1960, Eugene Wigner published a paper titled “The Unreasonable Effectiveness of Mathematics in the Natural Sciences.” In it, Wigner discusses one of the thorniest and most fundamental questions in physics: Why does much of (apparently totally abstract) mathematics later end up applying so well to physics?

He states, “mathematical concepts turn up in entirely unexpected connections. Moreover, they often permit an unexpectedly close and accurate description of the phenomena in these connections.” The paper drew a huge number of responses, up to and including the claim that this is the case because the universe itself is a Platonic mathematical object whose properties we are discovering over time.

I was reminded of all this by a talk shown at our weekly Lunch and Learn, “The Deep Synergy Between Testability and Good Design” from Michael Feathers (it even has a similar title!). Michael gives a few great, concrete examples of how “hard to test” implies “poorly designed.” For example, he describes the common pain of “I wish I could test this private method” as a hint to extract another class from an “iceberg class” full of private logic.

Why TDD is unreasonably effective

Michael left partially open the question of why testing pains so frequently indicate design problems, or why TDD leads to better design, which got me thinking about “Unreasonable Effectiveness.” I don’t have any grand unified theories of code to propose, but the question is an interesting one. It seems obvious that tests prevent regression and help ensure the correctness of your software, but why should it improve design?

My answer is something like: writing tests forces you to use your code as though you were already maintaining it. It brings directly to the forefront design pains that might otherwise wait for weeks or months to appear, before the code is even pushed.

Testing also forces the programmer to act as a client of their own code, rather than as someone with intimate knowledge of its interior workings. It’s easy, for example, for a class to accrue more and more direct dependencies on other classes over time, creeping into a god object that becomes a mess to maintain. Using the object when the setup has already been done by previous code can hide the problem. But unit testing a class with an enormous number of dependencies requires setting up (or at least stubbing out) every dependency, which quickly becomes a pain. So, writing tests in this case divorces the class from the context hidden by other code and brings those dependency problems to light.

Of course, you can “cheat” your way out of testing pains and still end up with badly-designed code. Michael Feathers demonstrates the cheat-y solution to his “iceberg class” example: just take the private methods you wish you could test, and make it public. Even then, testing can act as a barometer of code quality. In the case of the iceberg class, the unit spec will grow large and unwieldy from all the private logic that needs testing being stuffed into it.

All this seems (to me) to imply that “real” TDD, writing tests first, isn’t strictly necessary for reaping those design benefits. Test-first coding just forces the client perspective. With no written code, the programmer is free to focus on the way code will ideally be used and maintained, rather than considering it guts-first.


  1. J. B. Rainsberger

    You have absolutely hit on one of the fundamental effects of TDD. I emphasise testing all code through only its public interface (in languages that allow such a distinction) in order to amplify this effect.

  2. Ben

    Brian, thank you for describing one positive aspect of testing in a clear way.
    The problem of adapting TDD, e.g. in a Rails context, is that I have never found a concise, hands-on approach on how to start out. I never realized the basic points you are making ere such as, “what you will mainly be testing is your model”, or “testing is like writing a client for your application before writing the application”.

    Maybe you could follow this up with a tutorial aimed at TDD beginners? Something like “TDD for people who have a hard time seeing the benefits of TDD”?

    • Bree Stanwyck

      Ben, great idea! I may follow up with a tutorial applying TDD to Rails specifically.

      In the meantime, Gary Bernhardt has a great series of screencasts that show TDD (and a bunch of useful Rails-specific concepts) in action if you’re interested. He just recently stopped producing new screencasts, but he’s mentioned putting them up for sale à la carte in the near future. I would recommend the “Sucks/Rocks” series in particular, as it shows the development of an entire Rails app from the ground up, driven by tests the whole way.

  3. Keith Ray

    I wrote some advice on getting started with TDD here:

    Two other points I want to make regarding TDD and frameworks (Like Cocoa or Rails)

    1. frameworks can make TDDing code difficult, because your code will likely be very dependent on the state set up by the framework, as well as your code depending on concrete classes provided by the framework. The more dependencies there are, the more you have to mock out (for example mocking stuff out to avoid global state messing up your tests.)

    2. if you don’t know how the framework is going to call your code, you can’t simulate how your code will invoke your code. So you’ll likely need to “spike” throw-away code to find out how the framework really works (which is often somewhat different from its documented behavior or how you interpreted its documented behavior).

    Regarding Rails, there are a lot of online resources to TDD in Ruby and Rails, but some of it doesn’t really “get” TDD — those resources are actually writing system tests rather than TDD’s “microtests”. (Google “microtests” for more info.)

Leave a Comment

Join the discussion. Do not worry, your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>