Does TDD slow you down?

When you first start out with TDD, development will be much harder and much slower. It will practically grind to a halt. This is because you are learning. I’m not as interested in this part of the discussion. Any time you are learning something new, you will go slower. The more interesting question is, is it worth learning? Does it still slow you down once you become competent?

The truth is, you may never be as fast with TDD as you were without it. That’s a sign that you were going too fast. You weren’t finishing your work. You were writing code to get that specific feature working, and then moving on. You didn’t have to worry if the code you wrote was tightly coupled or had a poor interface, because you only had to call it once and that work is done. You definitely didn’t do much refactoring, because there was no safety net in place to alleviate the fear.

That pace is super fast and very addicting. But it is not sustainable. You can get things built quickly, but eventually maintenance becomes a nightmare, and your progress grinds to a halt. That’s because the same qualities that make code hard to test make it hard to change. Building code is easy, maintaining (i.e. changing) code is the hard part. TDD forces you to start feeling that pain early, so the cost gets spread out over the life of the codebase, instead of pushed back and back until you’re forced to deal with it (technical debt!).

So it’s about trade-offs. If you are working on a quick prototype, don’t write tests. It will slow you down! It’s ok to admit that. But it’s fine, because tests for a prototype won’t provide value. But if you are building something to last, write tests. It may slow down your initial velocity, but it will even out over the long-term life of the project.

Have you been trying to do TDD and it still feels like it’s slowing you down too much? Does it seem like your tests are doing more harm than good? Are you still waiting to see all these supposed “benefits” of TDD? I’m working on some materials to help you level up your TDD skills so you can start loving your tests instead of hating them. And I started a newsletter so we can have a conversation about the pain you’re feeling, and I can let you know as soon my TDD materials become available.

How to test your tests

One of the benefits of writing your tests first is that you will always see the test fail. This is important, because you can’t trust a test you haven’t seen fail. Think about a line of code like this: assert a = 3

Of course, you meant to write a == 3, but you may not realize that if it’s in a test that you wrote to verify already-working code. It would pass, and you’d assume it passed because the code it’s testing really did set a to 3. But if you wrote and ran the test first (or commented out the working code to see a test failure), you’d notice that the test was passing when it shouldn’t, and fix the bug.

Watching a test fail is one way to test your tests.

But don’t just see a red/failing test and run off to make it pass. Pay attention to the failure message. You could have a different bug in your test that’s causing the wrong failure. Maybe due to a syntax or logic error. So if you don’t get the failure you expect, that’s another sign that your test may have a bug.

Now when you see a failure you expect, only write just enough code to fix that specific failure. Is your “makes a equal to 3″ test failing because the module is missing? Don’t implement the entire module, just create it. Then watch it fail because the function is missing from the module. Now don’t implement the entire function, just declare it. And so on. Keep fixing only the immediate failure until you hit green. Does the implementation feel complete? If not, you need another test.

It may feel silly at first, but if you train yourself to always take these micro steps, you can be sure that every line of your code is actually being tested. If you take large steps, the chances increase that untested – or even superfluous – code sneaks into your system. Fixing only the current failure tests your tests for completeness.

So while you’re in the “red” step of “red, green, refactor”, remember to keep an eye out that you’re red for the right reason, and don’t try to jump straight to green, just fix whatever is making you red right now. Eventually you’ll get there, and you’ll feel super confident in your code.

Do I have to write the test first?

To many, writing the test first is a requirement of TDD. It’s how I prefer to do it, but I don’t believe it’s a requirement, especially when starting out.

But that doesn’t mean I’m suggesting you go ahead and code away willy nilly and then write all the tests when you’re done. You still need a tight feedback loop. So how do you get that if you aren’t writing the tests first?

Using small steps: write one slice of code. Does it work? Good, now comment it out! Then write a test that will only pass with the code you just wrote.

Now run your test and watch it fail. This is an important step. If you haven’t seen a test fail, you can’t trust that you’re actually testing what you think you’re testing.

Now uncomment your code. Does the test pass? Good. Now you can refactor. Does the test still pass? Good! Now commit and repeat. I sometimes call this comment-driven development. I’m sure I’m the first person to think of it. I’m very clever.

If you stick to this style, eventually you will start to anticipate how to design the code you’re writing so you can easily test it. Then you may decide it’s easier to just go ahead and write the test first. Welcome to the club.

TDD, Micro Steps, and Backing Up

TDD is a way to think through your requirements incrementally by writing a test for one small piece, writing just enough code to get that test to pass, refactoring, and then moving on to the next small piece.

As you’re growing your code in this way, you should be zoomed way in. Taking tiny, micro steps. The time from failing to passing test should be measured in seconds, not minutes. It’s a feedback loop and it should be tight. You don’t want to waste time poking around in the dark.

This puts excellent design pressure on your system. The only way to keep that tight feedback loop moving is by writing code that is loosely coupled with a single responsibility. Otherwise it will be too hard to test.

This is why TDD is more about design than it is about verifying working code. But that doesn’t mean you can ignore design completely and let your tests lead you blindly somewhere without thinking. You will need to zoom back out every once in a while. You will need to put your knowledge of good design principles into practice and think critically about your code beyond what’s only easy to test.

I’ve been in many positions where my tests painted me into a corner. Or my feedback loop starts slowing down as complexity starts spiking, or the easiest way to test something would result in an obvious code smell. When that happens, I back up.

The ability to back up is another reason to keep the feedback loop tight. You should always be able to easily jump back any number of steps to working code and try a new path.

Yes, you still have to choose your path when you TDD. It’s called test-driven development, but you’re still the driver, not the tests. They are a tool you use to drive out some desired behavior, and there’s usually going to be multiple ways to write tests to get there. Use your design sense to make the best choice. If you don’t like where you ended up, back up. And keep your feedback loop short so backing up is no big deal.

tdubs: better test doubles for python

A couple things have been bothering me about python’s unittest.mock:

Problem 1: Stubs aren’t Mocks

Here’s a function (that is stupid and dumb because this is an example):

def get_next_page(repo, current_page):
    return repo.get_page(current_page + 1)

If I want to test this with unittest.mock, it would look like this:

def test_it_gets_next_page_from_repo(self):
    repo = Mock()
    next_page = get_next_page(repo, current_page=1)
    self.assertEqual(next_page, repo.get_page.return_value)

What bothers me is that I’m forced to use a mock when what I really want is a stub. What’s the difference? A stub is a test double that provides canned responses to calls. A mock is a test double that can verify what calls are made.

Look at the implementation of get_next_page. To test this, all I really need is a canned response to repo.get_page(2). But with unittest.mock, I can only give a canned response for any call to repo.get_page. That’s why I need the last line of my test to verify that I called the method with a 2. It’s that last line that bothers me.

If I’m writing tests that explicitly assert that specific calls were made, I prefer those to be verifying commands, not queries. For example, imagine I have some code that looks like this:

# ...
# ...

with tests like this:

def test_it_publishes_the_article(self):

Now the assertion in my test feels right. I’m telling the article to publish, so my test verifies that I sent the publish message to the article. My tests are verifying that I sent a command, I triggered some behavior that’s implemented elsewhere. Feels good. But wait…

Problem 2: Public API conflicts

Here’s the other problem. Imagine I had a typo in my test:

def test_it_publishes_the_article(self):

Notice the extra “t” in “assert”? I hope so, because this test will pass even if article.publish is never called. Because every method called on a unittest.mock.Mock instance returns another Mock instance.

The problem here is that python’s mocks have their own public api, but they are supposed to be stand-ins for other objects that themselves have a public api. This causes conflicts. Have you ever tried to mock an object that has a name attribute? Then you’ve felt this pain (passing name as a Mock kwarg doesn’t stub a name attribute like you think it would, instead if names the mock).

Doesn’t autospec fix this problem?

autospec is an annoying bandage over this problem. It doesn’t fit into my normal TDD flow where I use the tests to tease out a collaborator’s public API before actually writing it.

Solution: tdubs

I decided to write my own test double library to fix these problems, and I am very happy with the result. I called it tdubs. See the README for installation and usage instructions. In this post I’m only going to explain the parts that solve the problems I described above.

In tdubs, stubs and mocks are explicit. If you want to give canned responses to queries, use a Stub. If you want to verify commands, use a Mock. (you want to do both? rethink your design [though it’s technically possible with a Mock])

A Stub can provide responses that are specific to the arguments passed in. This lets you create true stubs. In the example above, using tdubs I could have stubbed my repo like this:

repo = Stub('repo')

and I would not need to verify my call to repo.get_page, because I would only get my expected next page object if I pass 2 to the method.

With tdubs, there’s no chance of false positives due to typos or API conflicts, because tdubs doubles have no public attributes. For example, you don’t verify commands directly on a tdubs Mock, you use a verification object:


After hammering out the initial implementation to solve these specific problems, I ended up really liking the way my tests read and the type of TDD flow that tdubs enabled. I’ve been using it for my own projects since then and I think it’s ready to be used by others. So if you’re interested, visit the the readme and try it out. I’d love some feedback.

How to Practice

In my previous two blog posts (here and here) I mentioned practicing. The posts cover the reasons why we should practice, but they don’t mention how to practice. Here are some resources for that:

Google code katas. These are sample programming problems that you can solve over and over in any language. They aren’t about solving hard problems, they are about practicing and forming good habits.

Similarly, google koans for your language of choice. These may feel like beginner tutorials, but they are about practicing until you have the nitty gritty details of your language committed to muscle memory. offers guided programming problems with pre-written tests for many languages.

The Pragmatic Programmer recommends learning a new language every year. This is great practice even if you don’t get to use those languages in your day job. Try to pick languages in a different paradigm than what you’re used to. This will often help you see better solutions to problems in any language. You can use the previous resources to learn these new languages.

For meditation, google for a mindfulness mobile app so you can have it anywhere, and as a little reminder every time you look at your phone. I use The Mindfulness App on iOS. I like it because it offers both guided meditations in different lengths, as well as meditation timers I can set to any length of time I want. Great for starting out with 1 or 2 minute meditations.

There are many ways to practice, these are just a few suggestions. However you do it, remember the goal is to train so that when the pressure is on, you default to good code instead of bad.

Meditation as Training

When we train for something, it’s to form a habit. We want some behavior to become instinct. We want to do it without having to decide to do it.

Meditation is practicing focusing your mind on one thing and nothing else. Usually it’s your breathing. It’s training to bring yourself back from distractions, to be present and mindful of the actual surroundings and not the surroundings as you wish they existed. To put your full attention on what you’re doing right now instead of what you want to be doing even ten seconds from now.

That doesn’t mean you never think of the future. It means you’ve trained so that when it’s most important to be in the moment – which is when you’re most likely to not want to be in the moment – your instincts will kick in and you will handle it.

For example, you need to get some feature out ASAP. Here’s two scenarios for doing that:

Scenario 1) You hammer out a solution. You’re thinking about code, sure, but only enough to get the characters into your editor. Your mind is really on the goal: shipping this feature. You run the code and… it doesn’t quite work. Oh, you just need this quick fix. Still doesn’t work. Quicker fix. Still doesn’t work. Agh! Because the goal is just barely out of reach and it feels like it’s moving away from you, you go faster. Quicker and quicker fixes. It finally works! The code is garbage, but it works, and it took way longer than you thought it would. So you back away slowly and don’t touch it (ever) for fear of brining down that house of cards.

Our brains default to this scenario. It takes training and practice to fix this problem. Google mindfulness meditation and read up on it. A great way to start is by counting breaths: In, out, one. In, out, two. In, out, three. Go up to ten, then go back down to one. Repeat. When you do this, your mind naturally starts wandering: thinking about what you should be doing instead, all the tasks on your plate, that stupid thing you did in the past. This is the part you’re training for: realize your mind is not in the present. Don’t get frustrated that you can’t seem to meditate “right”. Acknowledge that thought. Maybe even sit with it for a couple seconds, and then let it go. Bring yourself back to the breath. If you lost your place, start over. It’s not about finishing, it’s about the process.

If you make this a habit and practice it daily, it will help you the next time you start feeling stressed. Your breath becomes a trigger to bring you in the present so you can do your best work.

Start with 1 minute. It’s very easy to tell yourself you don’t have time to meditate, but everyone can spare 1 minute. When a minute becomes habit, try two. Then three. I’m only up to four minutes myself and am already seeing huge benefits.

Scenario 2) In this scenario you’ve trained for this. Your stress at the beginning of the cycle becomes a trigger and instead of going faster, you stop and focus on what you’re doing right now: I want to get this feature out. Ok, that’s a thought about the future. Acknowledge it and let it pass. What am I doing right now? I’m writing this line of code. Now I’m writing this line of code. Now this line.

In the first scenario, it felt like you were moving faster, but because you were focused on getting somewhere faster, you wrote bad code, which actually made you get there slower. I’m willing to bet the second scenario got you there in roughly the same amount of time, but with much less stress, and an end result that has less bugs, and is more maintainable.

Excuses for bad code

“The client keeps changing requirements!”

“This deadline is unrealistic!”

“This legacy code is too hard to work with!”

Things like these are legitimate problems that are often used as excuses to write bad code. You feel pressure to go faster, so maybe you skip the tests. Maybe you write very long functions. Maybe you let the conditionals get nested into a web of complexity that works for now but will be impossible to untangle later.

You do what you have to do, but don’t fall into the trap of thinking it was totally out of your hands why the code is now hard to maintain. The truth is, when you felt the pressure, you fell back on what was comfortable so you could go faster. If writing bad code is comfortable, you need to practice until writing bad code is uncomfortable.

A professional sports team practices so they don’t fall back on bad form when the pressure is on. A professional programmer should do the same. Your goal should be for good code to be your comfort zone.

That doesn’t mean you can never cut corners when you have to, but do it mindfully. Don’t use one of those excuses to justify your lack of skill. Notice it, and then work on fixing it.

Lots of little objects

I like to use abstractions that lead to lots of small objects that do little jobs communicating with each other to do bigger jobs. The common argument I hear against this is that it makes the code hard to change, for two reasons:

1. “The work is spread out into too many places”

The Single Responsibility Principle says that a class should have only one reason to change. The inverse of that is that if multiple things will change for the same reason, they should probably be in the same place. If you are making a change that requires editing lots of things in lots of different places, you’re either making a very high-level change – in which case, this is expected – or your design needs work – those things probably should have been in the same place.

This doesn’t contradict “lots of little objects”, it provides a counter-balance for taking it too far.

2. “It’s hard to find where I need to make my change”

“I’ll start in my view… ok that’s calling a method in this file… oh that’s calling a method in this file…” and so on.

This is sometimes true, but I prefer it to the alternative of everything happening in this one method so it’s easy to find, but when I want to make the change I’m terrified because this method does so much.

I’m willing to trade that anxiety and potential for bugs for the cost of a bit more upfront time looking for the right place to make the change. Because when I find it, it’s only doing one small thing. I can change it with confidence.

This is the main reason I think using lots of small objects enables change. And ability to change is the best indicator of a good design.

Sandi Metz has a great talk on this subject.

TDD Rules!

These are some rules I like to follow when doing TDD. You can follow them too! Rules are fun!

  • Write your tests first. If you can’t, spike a solution, throw it away, and try again.
  • Test units in isolation. Use mocks to verify interaction between units. If this makes your tests brittle, refactor.
  • You don’t need to isolate your unit from simple value objects. So use more value objects.
  • If you feel like you can’t keep everything in your head, ask yourself if you really need to keep it all in your head. If you do, you need to refactor.
  • Each branch of logic should be covered by a unit test. If that makes you feel like you have too many tests, your logic is too complicated. Refactor.
  • If you ever feel the need to only run part of the unit test suite, it’s too slow and refactoring is needed.
  • Unit tests should be written as if they are a set of requirements – or “specs” – for the unit being tested.
  • Each test should test one and only one concept. That doesn’t always mean only one assertion.
  • When fixing bugs, make sure there is a test that fails without your fix, and passes with it.
  • Never push commits that contain failing tests. This makes it harder to revert, cherry-pick, and debug problems (e.g. with git bisect).