Pain And TDD

“We were doing TDD just like we’re supposed to, but at some point it started hurting, so we not only quit, we told everyone else on the interwebs that TDD doesn’t work.”

I met Kent Beck after a couple of years reading and writing around him, at the first ObjectMentor Extreme Programming (XP) Immersion, a wonderful week I’ll never forget, where I met so many awesome geeks and coaches. (Kent Beck, Mike Feathers, James Grenning, Bob Martin, Ron Jeffries, Martin Fowler, and more! You get the idea it was a special week?)

And Beck, answering a question, told the old Smith & Dale joke:

SMITH: Doctor, it hurts when I do this.
DALE: Don’t do that.

(The visuals include a funny wagging arm movement by Smith.)

Don’t Do That!

I chuckled. It was merely years before I grasped the deeper level of this old joke.

One might interpret it as saying “TDD hurts me, so I won’t do TDD,” but that’s actually the wrong way to interpret the joke.

You see, TDD works for some of us. And it works very well.

But it’s really important that you hear what I’m not saying.

I’m not saying that you didn’t — or aren’t — feeling pain. I’m not saying you weren’t — or aren’t — trying. I’m not saying TDD works for us because we’re smart and you’re stupid. 

What I’m saying is that you are not responding to the pain you’re feeling in the TDD way.

Pain Means Something Is Wrong

TDD is not painful.

I don’t mean that it’s not painful in the sense of “you’ll just feel a little pinch”. I mean it’s not painful at all, really.

So when we feel pain while doing TDD, something is going wrong.

The somethings that could go wrong are exactly three:

  • You are working in legacy.
  • You are learning.
  • You are working with code that needs refactoring.

That’s it. There are really no other situations where TDD should be causing pain.

Legacy Pain

TDD can leverage you out of legacy. I’ve done it. But working in legacy is just painful.

The key to successful legacy work is to become a master of extraction refactorings. Before you TDD a change, extract the change site so that it is optimal for microtesting.

When you can’t write a real microtest because of the location or accessibility or complexity, either move, extract, or simplify the code via refactorings. It’s not hard, but it’s tedious as hell, and it takes a certain boldness.

This will help. It will ease your immediate TDD pain, and it will — very gradually — lift you out of legacy work altogether.

But yeah. Legacy hurts for as long as it’s legacy, and there are not two ways about it.

Learning Pain

Most of the time, learning is a source of great joy to us. After all, we’re geeks. That’s what we do, is learn things and delight in them.

But there are some situations where learning hurts. Most of these are situations where you have to learn something while 7 people are shouting at you that if we don’t fix this we will simply all die. Let me tell you, that’s gonna hurt no matter what you do. Suggesting, of course, that we teach people not to do that.

There’s another time when it hurts. It hurts to learn when your current goal is too far over your head.

I have worked from time to time with folks with weak TDD, weak confidence, and weak knowledge of a language or a domain. That’s a painful combination.

If you are stuck at the incantation stage in a language, where you’re just barely avoiding syntax errors, and the fundamentals of the language are not yet within your grasp, it’s sore tempting to drop TDD altogether and just focus on typing the magic in correctly.

But it’s contraindicated. The reason TDD is a great design technique is because it organizes the design. That organization, plus simple extended exposure, is going to be the thing that ratchets your knowledge up most rapidly. Abandoning TDD will slow down your learning, not speed up.

Code Pain

Ahhhhhhh. Now we come to it.

I have a strong belief that code pain is the main reason people leave TDD behind.

And here’s what they’re doing wrong: gritting their teeth and trying to play through it.

That’s not the TDD way. In TDD, when we feel pain we change the code until we don’t feel it anymore. And we add what we’ve learned to our daily codesense.

Codesense isn’t one trick, it’s a thousand. And it isn’t an algorithm. Codesense comes into play at large scales and at smalls. But it all comes from feeling pain and resolving not to feel it any more.

Coming soon to a blog near you: live-action codesense examples, where pain leads to refactoring leads to new design.

We don’t play through the pain in TDD.

We use it to change our behavior & build our codesense.

9 thoughts on “Pain And TDD

  1. Strictly speaking, “You are working in legacy” and “You are working with code that needs refactoring” are the same thing. Legacy code is often thought to be crufty because it’s old. In fact, it’s crufty because someone wrote it that way. That may be because many people have modified it over the years, but I’ve seen programmers writing “instant legacy” code.

    I get why you separated them, though. There’s a fundamental difference in feeling when you’re struggling with code someone else wrote a long time ago, and when you’re struggling with code that you just wrote.

    Good stuff in this post. It doesn’t have to be that way!

    • George, of course you’re right on all counts. The legacy/needs-refactoring distinction was precisely because in some cases that’s every minute of every day, while for others it’s just a sometimes thing. And yes, to instant legacy. Seen it so often.

      • We have other types of tests that verify the aaplicption from end to end (integration tests) which do hit the database, but for our unit tests, they do not hit the DB.When I mentioned state based testing, what that means is that I’m verifying that the properties of my object under test are what they’re expected to be after a specified operation. Interaction based testing means that I’m verifying that my object under test is calling methods and properties on its dependencies correctly. The interaction based testing makes it tough when I want to refactor the way I implemented something without changing its functionality.

  2. Name dropping [X]
    Talk From Authority [X]
    The ol ‘You Just Need As Much Experience as Me’ [X]

    Yep, TDD as Religion confirmed.

    “You can’t quantify ‘Codesense’, you just have to feel it..” said the Priest.

    It’s contemptible that you infer that people that feel that TDD is not THE RIGHT WAY are somehow lacking in your magical Codesense abilities. The height of arrogance to pivot from the point that TDD is always correct, and then work back that anyone that does not agree has something lacking, that you can’t then quite quantify. Convenient.

    • Wow! My first ever angry person! Cool. Just two quick thoughts.

      First, I haven’t said I couldn’t quantify codesense. Quantification isn’t finished, of course, but there are hundreds of tools out there that will assess code, starting with the warnings from your compiler and moving on from there. Much of the resulting critiques are actually compiled and quantified codesense. I saw one the other day that went so far as to recommend specific refactorings in specific parts of the code.

      Second, the things I say here are not inferences from imaginary disputants. As a coach, I’ve worked with dozens of teams, and have faced hundreds of people who tried TDD and gave up. The only authority I argued from is mine, and it’s considerable, though I hasten to say not god-like.

      Oh, one more thing. Relax. Geekery is fun. Arguing about geekery is fun.

    • Baxtor, I think you didn’t understand this posting. It’s not trying to tell you that TDD is always correct. It’s trying to help people who want to learn TDD but are struggling. In that context, if it hurts, it’s time to think about why.

  3. Hi Dave, Great article you’re right that on the DBA side we sommeites miss out on the cool stuff such as NUnit. TSQLUnit is pretty cool for getting started with T-SQL unit testing. You might also want to take a look at tSQLt ( One of the problems I kept running into with writing T-SQL unit tests was this: Suppose I have a table that is referenced by 5 views, and I need to write about 5 test cases per view to feel like I’ve really covered the functionality. This means, I have about 25 test cases for these views that insert data into that table. If I later need to add a constraint to that that table, or a non-nullable column, or similar change even if it doesn’t have anything to do with the views I am testing my test cases are likely to all break. That’s a lot of extra maintenance as you grow your test suite. tSQLt was written to handle these problems specifically. It has features, such as FakeTable and SpyProcedure which behave a little bit like the mocking frameworks found in other unit testing platforms for OO code.Anyway, it’s always encouraging to see more people getting involved in unit testing their SQL code!

  4. Pingback: Unit Testing Matters | Late Bound

  5. Yes the old body builder saying “no pain, no gain” also applies as much about ugly legacy code. The pain is the refactoring required just to get some basic characterisation tests in place, which is NOT doing TDD, just a pre-requisite for doing it.

    Just like doing press ups, it hurts like hell at first, but then you find yourself getting stronger and it becomes easier. Many people, but not everyone, reach the point where they start enjoying it, and it becomes a good habit.

Leave a Reply

Your email address will not be published. Required fields are marked *