Refactoring for testability

What’s exactly is refactoring and can it improve our application testability?

Photo by Caspar Camille Rubin on Unsplash

What refactoring is?

As Martin Fowler point in his book “Refactoring”:

Refactoring is a controlled technique for improving the design of an existing code base. Its essence is applying a series of small behavior-preserving transformations. [..]

So refactoring is about improving existing code but without changing its behaviour, by small transformations.
For example, this could be refactoring activities:

  • renaming variables and methods parameters to be more meaningful
  • breaking large methods into smaller and more focused methods
  • moving responsibilities between components
  • extracting interfaces from classes

Refactoring aims to create more readable and understandable code without introducing any behaviour changes. We shouldn’t call activities like:

  • adding logging or error handling (change behavior)
  • reimplementation of component (redesign/rewriting, big scope)
  • extending components (adding new functionalities, change behavior)
  • rewriting implementation (redesign/rewriting)

as refactoring.

What should be the scope of refactoring?

We should only refactor the code we are working on.
If we spot the “ugly” code in a class that is not related to our current assignment, we shouldn’t do anything with this code. Just let it as is.

Our entry point should be method or function and we shouldn’t go out of class scope. As changes we introduce should be small and focused, the scope should be the same.

It’s good to limit our refactoring changes to only a few classes. It helps a lot not to get into a situation where we made more refactoring changes than increment changes. Fewer changes mean more focused code review.

If refactoring aims to change public API (for instance rename method), a better option is to introduce the new name and mark the old method as deprecated.
This way there will be no shadowing changes someone needs to review. After we finish developing our feature, we can create a new commit (pull/merge request) with only API changes (method rename). At the end of the renaming process, we should remove the deprecated method.

When do refactoring?

Refactoring should be a continuous process. We should consider it as part of your daily work. Just like writing unit tests before writing code ;-).

We shouldn’t expect that we will have some task to do refactoring. We shouldn’t plan to refactor as a future chore to do. This should be part of each increment.

Edge cases?

We shouldn’t “waste” time refactoring when the project has a short lifetime.
For short projects, it’s better to ship functionalities as soon as possible. Making code more clear, when no one will back to it have little sense.

While refactoring when we identify the obvious bug, we shouldn’t do anything to fix them. We should continue with changes for readability and understandability.

Refactoring aims to create more readable and understandable code without introducing any behaviour changes.

We should put some comments about the identified bug in the code and create a task to fix it in our task management system.

What about testability?

Should we refactor the code if we haven’t unit test covering the code we amend?

I think we should. If we have a unit test, we can feel more confident doing refactor but in my opinion, it is not required.

In the case where we haven’t unit tests, we have to be very careful about changes to not introduce a regression. Small and focused should be our priority. Also, our refactoring activities should be reversible.

For the above point, code review is very important. In general, someone should evaluate our changes. What is readability/understandability improvement from our perspective can confuse someone else.

Could refactoring improve testability?

I would say yes.

Refactoring improves code from a code reader perspective.
Refactoring activities (like for instance extracting interfaces) could also make classes more testable (or event testable if they weren’t before).

Writing unit tests for refactored components shouldn’t be threaded as refactoring activity, but these activities can open a way to write unit tests for components.

Go deeper?

If you like to explore more about refactoring for testability I encourage you to take a look at:

  • Removing singleton
    Let’s check what can we do to improve code testability by removing singletons methods calls from methods.

iOS Developer