Slowly and Steadily Understanding the Value of TDD
When I started in professional software development, I had only faintly heard about tests—I had never written one myself. The kinds of apps I built before I took development professional, rarely required maintenance (after all, who is going to maintain a calculator or a quizapp?). Also, the project I landed in didn’t have any automated tests. It did have a test plan, though—a spreadsheet listing hundreds of tests that a dedicated person ran manually after every release.
I was new, so I didn’t question the process; I simply assumed that’s how things were done. Over time, however, I began to see that these manual tests were pretty limited because:
1. They weren’t exhaustive.
2. The human in the loop might miss some tests.
3. They were slow to execute, often taking days during the release cycle.
Only after a few years did I join a project that actually had tests. This change coincided with my growing involvement in Laravel. While reading through its documentation (which I read cover-to-cover), I discovered the testing section. The great thing about Laravel was how easy it was to write tests—you didn’t need a lot of setup before you could start running them.
I started to realize the value of tests, yet I still wasn’t writing tests for my own code. Why? They felt like a chore, and I believed that code with tests would take longer to write than code without tests.
That “tests take longer” argument might hold true for MVPs or small projects where you’re not sure if the project will take off. But when you’re building something that will be maintained for a long time, it’s a different story.
As I moved through different projects, I noticed a pattern: projects with tests were much easier to maintain and work with.
Eventually, I began writing tests for my code—although I still didn’t write tests first. I would write the code and then write tests for it. I wasn’t alone in feeling that writing tests first was almost impossible. I even recall an interview with someone much more senior than me, who agreed that writing tests first didn’t solve the problem. I explained that you’d still have gaps in your understanding of the code’s goals; it’s better to write tests afterward so that you at least know what you’re testing.
For several years, I continued to write code first and tests afterward. Over time, however, I realized that the biggest advantage of writing tests wasn’t just that they caught bugs—it was that they helped me remember and understand the code better. Writing tests first forces you to define conditions upfront so you don’t have to keep them all in your head.
I experienced this firsthand when working with a messy codebase where I had to write tests. I had to read the code multiple times to understand what it was doing before I could even write tests. Consider this example of a system of URLs:
• host/location/category
• host/category/subcategory
• host/location/subcategory
• host/subcategory
• host/location/category/subcategory
In this system, the first segment is dynamic—it can represent a location, a category, or a subcategory. The second segment can be a category or subcategory depending on whether the first segment was present and what it was. This led to many different checks, and I would often get confused or even forget what a particular piece of code was for—sometimes removing it, only to later realize it was needed.
By defining tests first, I could map out all the different paths the code might take. Instead of keeping all those scenarios in my head, they were clearly documented in the tests.
Additionally, writing tests first forces you to write testable code. For example, I used to extract methods and make them private or static just for convenience. However, when writing tests first, you quickly learn that you can’t easily test private methods or mock static methods.
So if you don’t yet see the value in TDD, I can understand. It took me roughly ten years to fully appreciate the benefits of TDD—and I’m still learning. There are still times when I hate writing tests first and end up skipping them, only to deal with issues later. But I believe that’s more of a personal challenge than a problem with TDD itself.