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

Published: 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.

Software Engineer

Tags: CI/CD, TDD, Testing

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.

Skip to content