Test-Driven Development: all about the cycle

Daniele Scillia (Dan The Dev)
Dan the Dev
Published in
4 min readFeb 18, 2021
Test-Driven Development helps developers to split big problems into smaller ones

Test-Driven Development is a software development practice where you write the test that fails first, then the code to pass that test. It is based on a development cycle to iterate: RED — GREEN — REFACTOR.

Each of us had to deal with legacy code at least once in their experience, and when I say “legacy” I borrow the definition of legacy code from Michael C. Feathers’ book “Working Effective with legacy code”, meaning code not covered by automated tests.

Leaving aside the topic of refactoring legacy code, I want to focus instead on how we can avoid leaving legacy code ourselves to future colleagues. You might think writing automated tests would be enough, even if written after the production code; that could be right from a practical point of view, but the real level-up is writing tests first to use them as a guide in the code design process.

This is the basis of Test-Driven Development.

Test-Driven Development

TDD, then, is a code design technique; in this practice, you write a test that fails first (red), then the code to pass it (green). The idea is to be guided by the tests in writing the code and, consequently, in the design decisions we make during the process. In this way, the resulting design will be a direct consequence of the behavior we want to implement (tests should verify business behaviors, not technicalities).

At the conclusion of the TDD work cycle, we have a third phase that is decisive in making the practice a success: refactoring. It’s very important to refactor repeatedly at each turn of the cycle so that you can keep the code well-structured as development progresses and the codebase grows.

The Red-Green-Refactor Cycle

In detail, the TDD cycle is divided into three phases:

1. RED — Write a test that fails: in this first step, we write a test; it is important to remember that tests should verify behaviors and not technical aspects.

2. GREEN — Write the code needed to pass that test: write the simplest implementation possible so that the behavior expected by the test is verified. To pass this phase you have three main implementation strategies: Fake it (return the expected result directly, hardwired into the code), Obvious Implementation (implement the simplest possible solution), Triangulation (exploiting a triangulation with different tests on the same behavior, write more generic code).

3. REFACTOR — Improve the design: modify the code you’ve written to improve the design; the first aspect to improve is definitely the duplication (DRY: Don’t Repeat Yourself) but be careful not to create abstractions too soon; use the Rule of Three, that means waiting until the duplication appears the third time to create the correct abstraction. It is much harder to fix a wrong abstraction than a duplication of code.

Applying these three steps continuously will allow you to advance your development easily while maintaining complete peace of mind about how the software will work. Don’t underestimate the Refactoring phase; it’s important to improve the design on an ongoing basis.

TDD without refactoring is just half of the game.

Always remember to test and verify only one behavior at a time. Before testing integration between different behaviors, make sure you have fully tested each one. By “complete” we mean happy paths, sad paths, average values, and limit values.

The name of the tests is also important: they should describe a feature or business behavior. Avoid technical words and implementation details. The name of the test should not change if the implementation changes, because it should only describe the expected behavior. A good strategy is to give the test a temporary name and then choose the final one once all the test code is written.

The importance of making the method yours

The technique may seem almost trivial once you understand it, but it will become a great ally: its simplicity helps you especially when you are faced with a problem that you don’t yet know how to solve because it allows you to attack a large complexity, one piece at a time. Consequently, this approach will be particularly useful when you don’t yet have a clear idea of the implementation you want to achieve: the tests will guide you towards a solution.

You don’t have to worry if you don’t like the first solution it leads you to: it’s okay to try to get the tests green as quickly and easily as possible; afterward, thanks to them, you’ll be able to take advantage of the refactoring phases of the cycle to improve the design of your software, ensuring that you’ve maintained the correct behavior.

The most important thing is to make the principle of the cycle yours: having a test that verifies the behavior we want to achieve and go to a solution that passes that test, even in a trivial way, means quickly having code that works and does what we need it to do, returning positive feedback that results in great peace of mind.

At that point, it will be easier to think about how to improve the design while keeping the tests green (meaning not changing the behavior).

What’s the real point of TDD?

Test-Driven Development is the first step towards quality code, primarily because there can’t be good code without tests covering its behaviors. But test automation is just a part of this technique, at the heart of which we find the idea of writing tests before production code: tests represent the behavior we want to develop, acting as a starting point of the process and not as an endpoint. This leads not only to a better design with respect to the development goals but also to a more complete and conscious test coverage: writing tests after the fact would hardly have the same effectiveness, risking forgetting some test cases.

Embracing the TDD technique opens the way to solving problems more easily and safely.

--

--

Daniele Scillia (Dan The Dev)
Dan the Dev

Software Engineer @TourRadar - Passionate Dev, XP Advocate, passionate and practitioner of Agile Practices to reach Technical Excellence