Do you guys really do TDD?
I was browsing Reddit yesterday (big mistake) and stumbled across a post asking how many people are doing test-driven development in their work. OP had experience at both software agencies and startups and felt that even though management recognized the value of writing tests, in his experience it was always treated as a burden.
Reddit - The heart of the internet
I always find these conversations interesting. When coaching teams and individuals on how to make the most of testing (test-first or otherwise), there’s always a few critical things that I focus on.
- The failure case is the most important. You should be writing tests that fail in ways that are useful to the person that seems them fail. There are lots of ways for tests to fail. Consider how each test will fail when writing it.
- Code is a liability. Don’t get me wrong; test suites can be tremendously valuable. They can also be huge time-sinks. When people complain to me about their test suites, I often open up to find them full of unnecessary or redundant tests. Less is more when it comes to testing.
- Test for a reason. I once had someone explain that they were frustrated that they had to write a lot really tedious, low-value tests. They didn’t actually have to write them; they just felt like they did. There are lots of reasons to write (and not write) tests. You can’t write good tests without knowing why you are writing them (and “my boss says I have to” isn’t a good reason).
The comments on the post were extremely varied. Some people are writing tests (or not writing tests) for some really strange reasons. I wanted to comment on a few responses that I found the most interesting.
The Question
Before I dive into the notable answers, there’s something in the question that stood out to me. The author says they tried TDD in projects outside of work, but dropped it in because it was slowing them down.
I tried applying TDD in some side projects, but I dropped it because it was slowing me down and the goal wasn’t to master TDD but to ship and get users.
While I find TDD keeps my side projects going at a steadier pace in the long run, I think OP was entirely right in dropping it. TDD is a skill that takes time to learn. If you’re not comfortable with the technique, it’s definitely going to slow you down. If learning TDD isn’t the goal, skip it.
The Answers
At the time I’m writing this, the “top” answer is from Dave Copeland:
Tests don’t slow down sprints. Manually checking if your code works definitely does.
I totally agree that well-written tests don’t slow down sprints, and that manual checks can really kill your team’s productivity. It’s also the case that many teams have built themselves completely soul-sucking (and productivity-sucking) test suites, though. I’m sympathetic to that experience.
There’s a response to Dave’s comment about how TDD can slow down exploratory coding (no one said you have to do TDD when doing exploratory work) and that you still need to manually test your code (no one said you didn’t). Moving on.
The next answer is from someone who writes tests, but doesn’t normally write them first:
Not really, no. I usually write some functionality, think about the important happy path and sad paths worth testing, then write tests for those. I’ll often write out the test definitions first and skip them, just to capture my tests as “TODOS” in a way.
Edit: Tests are definitely a must. That’s been the minimum bar for 10 years at this point.
By doing this, you sidestep some of the great feedback you can get from your tests, and it’s easy to overlook im portant considerations that lead to valuable test suites, so I don’t recommend it. I’ve met a ton of developers that work like this, though. I wouldn’t be surprised if it’s the most common approach.
The third comment comments on how they treat frontend and backend code differently when it comes to tests. I’ve heard this one before:
I always use TDD with backend code. Frontend can be more hit or miss depending on the frontend framework. But whenever I can, I write the tests first. It feels like cheating, because once my code passes the tests, then I’m done. I know the feature works. I can move on to the next feature or refactor with confidence. It may be a slightly longer upfront cost, but the time it saves me troubleshooting bugs or redoing work more than makes up for it.
Especially in the web development world, backend stuff is pretty easy to TDD. You’ve got clear inputs (HTTP requests), outputs (HTTP responses), and global state (databases). All of those things can be controlled and tested, and the code you write is mostly just gluing all these things together. It’s inherently easier to test.
In the frontend world, you’ve got all kinds of other stuff to worry about. Once you involved the browser, you’re dealing with a distributed system and all the fun problems that come with that. Web page state is tricky in ways that aren’t overly rigid or prone to failure. Different frameworks provide different levels of support for different kinds of testing.
The first comment that really threw me for a loop was this one that mentions AI:
With how good AI is at writing tests, there’s almost no excuse to not write tests alongside development. Whichever way you decide to do it (tests first then code or vice versa), it will really help lock in your logic. The only downside is if you envision your logic to work one way, write tests for it, and then realize you need to change the logic/interface to it, then it becomes a huge PIA
I do not what to “lock in” my logic. One of the biggest complaints you see around test suites is that a single change to the behaviour of an object somewhere requires cascading changes across the test suite. Suites like that are “locked in” and I hate it.
This person also has either a wildly different experience than I’ve had with AI writing tests, or a completely different definition of the word “good” than I have. I suspect it’s the latter. I’ve been doing my best to try out the various “AI” coding tools and they are abysmal at sticking to writing high-value tests. LLM training data is cut from the same kinds of test suites I’ve encountered in the wild, and they aren’t good.
When I lean on agentic tools (which is less and less, for reasons like this), I constantly have to tell them to stop writing redundant and unnecessary tests. I promise you that this isn’t because my “rules” aren’t good enough.
One common complaint about TDD is that you need to know the code you’re going to write if you’re going to write the tests for it first. One commenter said as much:
If I know exactly what the implementation is going to look like before I start then I’ll write tests first and red-green-refactor.
If I don’t know how I want to build something I’ll riff on the implementation until I have something I like and then I’ll write tests to cover it.
You do not need to know what code you’re going to write to use a test-first approach. You might if you’re going to write extremely rigid, mock/stub heavy tests that “lock in” your implementation, but that’s not what you should be going for.
It’s okay if the first test you write ends up being “wrong”. It’s the first step in a process of gaining feedback. Sometimes you go down a path and find that the implementation you had in mind is impossible. You aren’t expected to write the right test every time. Each test is an opportunity to learn, potentially moving closer to your goal. You are always allowed to change your mind.
That said, this commenter’s second sentence is spot on. There is nothing wrong with using other design tools (like “riffing”) to help make progress. I like to point people to Growing Object-Oriented Software Guided by Tests as one of the best books on test-driven development. In it the authors encounter a situation where TDD doesn’t help them move forward, so they reach for a different design technique, CRC cards. Another technique I really like is called “reading code” and I use it all the time.
I’ll be the first to admin that I’m a “TDD guy”, but I’ll never tell anyone that they have to do TDD all the time. I just think it’s a good default way of working. By all means, use whatever techniques make the most sense for the problem at hand. If you’re writing a sudoku solver, TDD is probably not the best approach.
I liked this comment:
Depends on how long code will live. If you have an app that is 5+ years old, if there are no tests it’s a catastrophe but if it’s well-tested it can actually be safely modified
There’s a module in a codebase that I no longer work on where I added a test file, but it had no tests, only a comment explaining a few things:
- I thought it tremendously unlikely that the module would ever need to change.
- If it ever did need to change, it would need manual testing anyway.
- If it broke, it wouldn’t immediately affect customers and the organization would notice immediately.
- Writing tests for the module would either be extremely difficult or result in slow, low-value tests.
Code only needs to be easy to change relative to how often it needs to change. A perfectly modular, pluggable design is a waste of effort in code that doesn’t need to change. Similarly, tests are a tool for supporting change. It’s okay to ask yourself, “what will happen if I don’t test this?” and skip tests if the answer is “nothing bad” (and the tests are particularly hard to write).
Before I get too deep into the low-upvote comments, I’ll finish on blowmage’s succinct answer, perhaps my favourite:
Yes.
In Summary
If you sift through the comments, you’ll find a mix of people with different experiences with testing. Some commenters have leaned into the test-first paradigm and found value in it. Some commenters found frustration in the experience and chose write their tests after the fact. Most seem to work at organizations where some amount of testing is expected.
My approach in teaching effective testing, which you’ll hear me talk about in some of my conference talks, is focused on that last point. If your organization requires that you test the code that you write, why not get the most value you can from that? How much testing you do and when you do it depends a lot on the type of code you work on, but there are always opportunities to make the testing you do more valuable.