Refactoring In TDD: When Does It Happen?


For many developers, software development is a puzzle.

You have to think about what the pieces are and how they go together. To create a testable design, you need to understand the whole system and understand all the moving parts.

But at some point, you need to stop being so focused on understanding everything and start building! To build quality software, you can use test driven development. And refactoring is a crucial part of the TDD process.

The question is: how do you know when it’s time to refactor? When does refactoring happen in TDD?

Refactoring in TDD happens after you write a failing test (red phase) and fix the test by implementing missing functionality (green phase). The final step is to refactor your code, but only after all your unit tests pass.

This article will teach you how to build a solid foundation for your projects and avoid common mistakes when refactoring your test-driven code.

What is Test driven development?

Test driven Development (TDD) is a software development process that relies on writing a unit test before writing any production code. This prevents you from getting too far without a clear idea of your end goal.

On the surface level, it’s simply writing test code before implementing the functionality. However, a deeper understanding of TDD reveals a more complex set of practices. It also involves refactoring your code to remove code duplication and enhance its quality.

TDD forces you to create an abstract plan for what your application should be doing and then forces you to break it down into small, incremental steps. As a result, the design is better. You’re thinking about the little details as they come up.

The TDD cycle has three phases:

  1. The red phase – is when you write a test case for the functionality you want to add. When your test fails, your code is incomplete, and you have to implement the missing functionality.
  2. The green phase – is when you write the minimal amount of code needed to make the test pass. As soon as your test passes, the implementation of this feature is done (at least for now).
  3. The refactoring phase – is the final step in the TDD cycle. It’s when you change or improve existing code without changing its external behavior. In this phase, you improve the design without breaking any functionality. You will know if you have broken a functionality if an existing test fails during refactoring. TDD naturally leads to high code coverage.
tdd

While developing a new feature, you must break down large tasks into smaller ones to take advantage of the incremental nature of TDD. For example, suppose you are writing a new feature for an application and the task requires several different classes to work together to accomplish a common goal. In that case, each class should be implemented and tested separately.

Then, when all the individual classes are working correctly, you can combine them into a single module and test with an integration test.

What is refactoring?

Just like a machine needs routine maintenance, so does the code.

Refactoring is a process of reworking code to improve its readability and performance without changing its function.

refactoring

In this phase, you can change the names of variables and methods. You can also extract a method from a larger one to make it more readable. You can also combine two or more similar methods to remove code duplication.

Refactoring is often confused with debugging, but they’re very different processes. Debugging is what you do when your program doesn’t work correctly. It’s finding and removing bugs that prevent your program from working correctly. Refactoring is what you do when your program works perfectly, but some parts are hard to understand or could be improved.

You should try to refactor early and often so that the code stays clean as it develops. I’m not saying you should refactor your code before it even works, but you should at least try to keep the code clean as development progresses.

Let’s say that you have a method like this:

public class ReplaceNestedIfStatementsWithGuardClauses
{
    public double GetInterestRate(ClientType clientType)
    {
        double result;
        if (clientType == ClientType.Business)
        {
            result = 0.05;
        }
        else
        {
            if (clientType == ClientType.ExistingClient)
            {
                result = 0.02;
            }
            else
            {
                result = 0.03;
            }
        }
        return result;
    }
}
 
public enum ClientType
{
    Business,
    ExistingClient,
    NewClient
}

It may look simple enough, but you can quickly improve it:

public double GetInterestRate(ClientType clientType)
{
    double result;
    if (clientType == ClientType.Business)
    {
        return 0.05;
    }
    if (clientType == ClientType.ExistingClient)
    {
        return 0.02;
    }
 
    return 0.03;
}

The second example is much more readable and easier to understand. I have refactored the code using guard clauses. I have a separate post where you can learn the step-by-step process I used to refactor the code.

When does refactoring happen in TDD?

For TDD to work properly, developers must know how to refactor their code without breaking anything else.

Refactoring happens in the TDD practice after you quickly implement the missing functionality and all tests in the test suite pass. The code after the first two steps might not be the production quality code. But that is ok. That’s why you have refactoring as the third and final step.

The goal is to get the initial messy code into a state where you can easily add new features and write new tests. In other words, you refactor the code to make it more readable, robust, reusable, and maintainable. Refactoring should be an ongoing process of every developer over time. The goal of TDD should be to allow you to have a constant focus on writing production-quality code in the very first place.

Refactoring is an essential step in any software development cycle, but it doesn’t always happen in the same order. In TDD, it happens as the final step. But in non-TDD practice, it can happen some other time:

  1. You can refactor before you add a new feature.
  2. You can refactor while fixing a bug.
  3. You can refactor during the code review.

As you might notice, there are endless opportunities for refactoring. But, unfortunately, refactoring is a step that most developers skip. It’s not always fun, and it takes a lot of time. But it will pay off in the long run if you don’t skip it.

How to avoid common mistakes when refactoring in TDD

When refactoring a class using TDD, you can run into some common mistakes that will slow down your development process.

Refactoring without tests

When refactoring, the goal is to introduce only minor changes that do not break anything. If the new code is broken, you have to go back and change it again until it works. This is called regression. If you decide to refactor without having tests for it, you can break something that worked well previously.

So, after all the effort you put into something, it is frustrating and time-consuming to go back and fix the bugs.

The solution to this problem is writing unit tests for every method you refactor.

Not refactoring enough or at all

Not refactoring enough or at all is a mistake that many developers make. The sooner you start, the better. It’s not a matter of “when you have time” or “when it feels right“; it’s a matter of getting into the habit.

It won’t feel like such a big deal if you only refactor once. However, if you do it twice a day, every single day, then it’s almost like clockwork. Pretty soon, you won’t even think about writing bad code because you’ll be so used to refactoring.

Refactoring too much

During the refactoring, it’s essential that you only focus on small tasks that you can accomplish in less than 10 minutes (such as renaming a variable). This way, you avoid the trap of endless refactoring and make sure you get the maximum value out of it. If you don’t have a time limit, you’ll do too much refactoring, and the refactoring session will lose its value.

Refactoring is a great way to get more value out of your code. But you need to do it continuously, and it will help you improve the quality of your code. The goal is not to set aside weeks for it, refactor half of the codebase, and introduce hundreds of bugs.

Refactoring in the wrong place

Don’t do any work that isn’t related to the task at hand. For example, don’t start refactoring some other code that is not part of your current task. This is one of the most common mistakes people make when they try to refactor.

It’s crucial that you stay focused on the task and not get distracted by other things.

I’m not saying you shouldn’t refactor the existing code. Of course, you should. But only after you’ve finished the task at hand. If you’re trying to refactor a function and your mind starts wandering to other classes in your code base, stop what you’re doing and take a break. Go for a walk or have a cup of coffee. When you come back, you’ll be more focused and able to get the job done.

By focusing on one task at a time, you keep the project moving forward.

Conclusion

There are plenty of reasons to refactor your code. Whether it’s improving the design of the legacy code, reducing technical debt, removing duplicate code, or helping you learn the codebase better, refactoring has a place in every TDD project.

The best time to refactor your code is when all your tests pass. That way, unit testing will prevent you from introducing unintentional bugs to your functional code.

If you want to learn more about refactoring and the TDD approach, the following articles will help you get started:

Recent Posts