TDD is Not for Me
Tags: beginners, testing, webdev, healthydebate
Published: 2019-03-15 15:45:05

---
title: TDD is Not for Me
published: true
description: Test driven development is thought of as a practice every developer should adhere to, yet many of us privately struggle to be productive using TDD. In this article we explore some of the reasons why TDD isn't always a great choice and can hinder your productivity
tags: beginners, testing, webdev, healthydebate
canonical_url: https://www.calhoun.io/tdd-is-not-for-me/
---

> *This article was original posted on [calhoun.io](https://www.calhoun.io/tdd-is-not-for-me/), where I write about Go, web dev, testing, and more.*


This will be the second time I've written about an [unpopular opinion I have about testing](https://www.calhoun.io/testing-is-not-for-beginners/), and I'm sure it will ruffle a few feathers. **I don't always practice Test Driven Development** (also known as TDD), **and I believe there are many situations where practicing TDD is more of a hinderance than a boon.**

I'm going to explore why I don't always practice TDD in a moment, but I want to be clear beforehand that this is not a TDD bashing article. In reality, I think learning about and trying TDD is incredibly beneficial for developers. TDD forces developers to think about development and design from another perspective; rather than focusing on, "How am I going to implement this?" they instead need to step back and think, "How would I use this function?"  While this change may seem minor, it can result in drastic improvements to a codebase.

*So why am I writing this article?*

I am writing this article because there are many developers out there who struggle with TDD and feel poorly as a result. Whether it is imposter syndrome or just feeling like they have a dirty secret they need to hide, those feelings exist in far more developers than most people realize because everyone is too worried to admit that they just can't make TDD work for them. This is made even worse when a developer hears others talking about how TDD is this amazing thing that made them so productive. In short, there are many coders out there who feel like crap and they shouldn't; TDD is a great learning tool but I can say firsthand that it isn't always effective, and in many cases it just hinders my ability to produce high quality code quickly.

Alright, now let's dig into why I don't practice TDD all the time.


## TDD focuses too much on unit testing

Test driven development is often taught as a process with three steps: Red, Green, Refactor.

The idea here is pretty simple:

- The Red step is where you write a test that will fail because it tests something you haven't implemented yet.
- The Green step involves writing code to make that test pass
- The Refactor step is where you restructuring your code into something more maintainable while using your tests to ensure you don't break anything. You DO NOT add any new functionality during this step.

I tried to follow TDD pretty strictly for a bit, but at times it just felt like a massive pain; it kept slowing me down in every once in a while rather than helping me be more productive.

I couldn't really put my finger on exactly why that was the case until I read this twitter thread by Jeffrey Way:

{% twitter 1102673086799855616 %}

*I only include the first tweet here, but you should go read the entire thread. It is well worth your time.*

Jeffrey puts into words what I have been struggling with for quite some time; TDD is hard to learn and grasp when we focus so much on unit tests and completely ignore the more complex scenarios that every developer is bound to run into sooner or later.

In nearly every case where I find using TDD troublesome, it almost always stems from me trying to unit test some code where I need to mock out everything under the sun.

TDD is supposed to help us take a step back from the implementation and instead focus on how the code might be used, but when I am writing unit tests where a bunch of dependencies need mocked out that is no longer true. I am forced to once again start thinking about implementation details like, "Will my code need access to a database?" and "What about encoding? Do we need to support multiple output formats? Should I inject a mock encoder for the test?"

It just doesn't feel natural to me to think about code this way. It isn't beneficial to start thinking about what dependencies I'll need before I even start to use them. Instead, I work better when I take a step back and write a feature test. Something like, "When I `POST` to the `/orders` path with the JSON `{"price": 123, ...}` I expect to get the following JSON back with an ID that matches the `ord_[a-zA-Z0-9]{8,}$` pattern."

Not only is this type of test incredibly easy to write - we just spin up an app, hit the endpoint, and check the results - but it is also gets me back into the correct mindset. I'm now thinking about how someone might use actually use the code; I'm thinking about another developer interacting with my API, or a real person filling out a form and submitting it.

There are obviously exceptions to this. For instance, if I'm writing a function to factor numbers TDD could lead to a reasonable solution to the problem. The key components here is that we aren't really focusing on mocks and dependencies; we are instead writing an isolated function and TDD can shine in situations like these assuming we don't fall into the second trap of thinking we have to write the absolute minimum amount of code at all times.

I'm also not saying you shouldn't ever write unit tests where you mock things. These tests can provide value in many environments. I just don't find myself practicing TDD as often when writing my unit tests. Instead, I unit test my feature tests and then write unit tests as I see fit.


## Not all code should be written one test case at a time.

TDD is often taught using the following rules:

1. You are not allowed to write any production code unless it is to make a failing unit test pass.
2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

*These rules were authored by Robert C. Martin (Uncle Bob) and can be found on the website: [butunclebob.com](http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd)*

While this can work in many situations, **I wholly disagree with the idea that all code should be written this way.**

What I expect to happen here is for someone to link me a video, a blog, or some other example where the developer uses TDD to derive some complicated algorithm. One popular example is [writing a function to determine the factors of a number](https://www.youtube.com/watch?v=AoIfc5NwRks). I've also seen articles where the author explores whether it is possible to derive something like quicksort via TDD.

Surely if these more complicated algorithms can be derived through TDD then it must work in all cases, right?

While TDD can be used at times to derive a reasonable algorithm, I have also seen countless instances where it has worked in the exact opposite way. By using TDD the developer derived an algorithm that was incredibly slow, inefficient, and sometimes even missed edge cases because it is nearly impossible to think of every edge case, let alone test them all.

The truth is, there are situations where you actually need to sit down and think about a little more than just expected inputs and outputs for a function. The easiest example to grasp is probably a sorting algorithm - while you might derive quicksort from a TDD approach, you could just as easily derive an algorithm that is far slower and less efficient. Even if you did come up with an efficient quicksort, most standard libraries use something a little more complex than this because it isn't as efficient to use quicksort with smaller sized lists.

Going beyond algorithms, there are also times where constantly context switching just hurts overall productivity. While it might work well for many developers to constantly write one test, then one or two lines of production code, test again, refactor, then repeat, I personally find this constant context switching a distraction. I find that I am far more effective when I :

1. Write a few test cases demonstrating the basic functionality I expect.
2. Spend time to think about how I might achieve that functionality.
3. Implement a rough version that gets my tests passing.
4. Refactor as necessary.

This is pretty similar to TDD, but it isn't quite the same and I'm sure if I taught it as my version of TDD many would tell me I'm "doing it wrong". 🤷‍♂️

## Wrapping up

I find TDD to be beneficial at times and I'm not saying we should abandon it. Instead, what I am trying to convey is that getting caught up in this strict set of rules defining what is and isn't TDD is a mistake.

We should instead take the lessons we can learn from TDD and apply them in the way that is most effective for ourselves. If that means we end up breaking a few of the rules, so be it. After all, the goal of TDD, agile, or really any development process is to make us better at our job, and if they aren't doing that then something needs to change.



## Want to learn Go?

Interested in learning or practicing Go? Check out my FREE course - [Gophercises - Programming Exercises for Budding Gophers](https://gophercises.com).

I also have some premium courses that cover [Web Dev with Go](https://www.usegolang.com/) and [Testing with Go](https://testwithgo.com/) that you can check out as well.