TDD in the real world: when to use it (and when not to)?

Rafael Buzzi de Andrade • November 28, 2024

Test-Driven Development (TDD) is one of the most well-known modern philosophies for achieving a high level of excellence in software development. Like any solid philosophy, however, TDD is sometimes misunderstood and misapplied, making it challenging to implement in certain environments.


The discussion about the importance of testing in systems development is nearly as old as programming itself. In the second chapter of The Mythical Man-Month, Frederick P. Brooks Jr. suggested, back in 1975, that half of the time spent on a task should be dedicated to testing. When the testing phase isn’t planned, developers often spend even more time on it unintentionally.


In the late 1990s, Kent Beck and others created the concept shown below:

This simple image is an effective tool that helps us better understand TDD. However, this simplicity can lead to overcomplications that make TDD impractical in day-to-day work, diverging from its original purpose. So many rules and behaviors are added to make it “true TDD” that it becomes unappealing to apply.


The very concept that your code should fail to compile before starting implementation doesn’t align with how some people think about problem-solving. Additionally, there are times when it’s simply not possible to write the test before the code. Both David Heinemeier Hansson (creator of Ruby on Rails) and Kent Beck (creator of TDD) share this perspective. Their thoughts can be found in this keynote.

If there are situations where TDD is impossible to use, when is it truly useful?


When to apply TDD?


Most TDD tutorials cover testing calculators, passwords, and other simple scenarios. Looking at software layers in modern applications, these TDD examples are often at the business layer and don’t account for more complex, real-world challenges.


Though not widely applicable in the usual workday, these simple cases can still provide clues about TDD. Their simplicity helps convey the main concept behind TDD quickly. That’s the goal of a tutorial. With a simple scenario, it’s easier to see all possible results of a piece of code. This lets us write test cases that cover all scenarios, including positive, negative, and edge cases.


And here lies our answer: to apply TDD, you only need to know how your code should behave.


In many cases, not all criteria are clear at the start. In fact, most projects are like this. Even in these situations, TDD can be implemented incrementally. The TDD approach itself allows for rewriting tests as many times as needed. Create the test based on what you know and update it as your understanding of the project evolves.


When TDD is not usable


If you’re receiving tasks with unclear acceptance criteria, it’s practically impossible to apply TDD.


Unlike community tutorials, a developer’s day-to-day involves delivering complete and complex services, not just business rules. If we only apply TDD at the business layer, the service has typically already gone through significant setup, with all its structures and protocols running. And what about tests for those parts? We need to think about tests before defining the layers of what we’re developing. It may feel more natural to implement TDD if we start even before opening the IDE (Integrated Development Environment).

David Farley, author of Continuous Delivery, once said: “It’s not about ‘Testing.’ It’s about ‘Specification.’ It’s not about ‘Testing Code.’ It’s about ‘Confirming Behavior.'”


When to use TDD: a practical example


It would be contradictory to discuss practical applications without giving at least one suggestion. So, consider using Postman (as we did for this client) or a similar tool as an MVP for TDD.


In the “Tests” tab, you can create acceptance criteria for your call to be considered successful. Set up the calls with the formats and fields you expect to receive. The test will fail because the project doesn’t even exist yet. Build your app and write a fixed object return. Your test passed. Now you can write your code, connect it to the database, make it return an actual object, and then write your unit tests. Or you could write the unit tests first, and then write the rest of the code and database integration—it depends on how you prefer to work.


If “real TDD” means writing tests before any code, we’ve just considered a scenario where we write tests even before opening the IDE. In cases where different people handle the backend and frontend, the frontend developer can write these tests and hand them over to the backend developer. This reduces a lot of rework that often occurs when the back-end and front-end meet.


Better still, with the right training, the product team could prepare the initial tests before the task even reaches the development team (whether back-end or front-end).


In any case, it’s important to understand that clear acceptance criteria are essential for well-applied TDD.


Rafael Buzzi de Andrade
Software Engineer


LET'S CONNECT

Ready to unlock your team's potential?

e-Core

We combine global expertise with emerging technologies to help companies like yours create innovative digital products, modernize technology platforms, and improve efficiency in digital operations.


You may also be interested in:

Por Adriele Radmann 3 de agosto de 2026
Discover why customer support is your strongest shield against churn. Learn how to connect support, product dev, and AI to protect recurring revenue.
23 de julho de 2026
See how Banco Inter migrated to Jira Cloud, cut $200K in annual costs, and boosted team efficiency with e-Core as its implementation partner.
Por Flávia Batista 10 de julho de 2026
There is a belief that runs deep inside IT operations teams: a noisy environment is a healthy one. If alerts are firing constantly, if tickets are piling up, if the on-call rotation is getting hit at 2am, that means monitoring is working. The tools are catching things. I understand where this comes from. In the early days of observability, silence was genuinely suspicious. A quiet dashboard often meant a gap in coverage, a misconfigured rule, something important slipping through undetected. So teams learned to treat volume as proof, and that instinct stayed long after the environment around it changed. But noise is not proof that monitoring is working. In most cases, it is proof that something upstream was never fixed. Automation at the wrong end When alert volume becomes unsustainable, the response is almost always the same. Leadership looks at the backlog (a thousand tickets a day, engineers buried, SLAs slipping) and reaches for automation at the remediation end of the pipeline: AI agents plugged into monitoring tools, scripts that fire when incident X arrives, routing logic that moves tickets faster. These are reasonable responses to an unreasonable situation, but they treat cost rather than cause. Most of those alerts should never have been generated. Processing them faster does not change why they exist.