TDD Refactoring – Why Write Tests for Code That Will Be Refactored?

legacy coderefactoringtddtesting

I am refactoring a huge legacy code class. Refactoring (I presume) advocates this:

  1. write tests for the legacy class
  2. refactor the heck out of the class

Problem: once I refactor the class, my tests in step 1 will need to be changed. For example, what once was in a legacy method, now may be a separate class instead. What was one method may be now several methods. The entire landscape of the legacy class may be obliterated into something new, and so the tests I write in step 1 will almost be null and void.
In essence I will be adding Step 3. rewrite my tests profusely

What is the purpose then to write tests before refactor? It sounds more like an academic exercise of creating more work for myself. I am writing tests for the method now and I am learning more about how to test things and how the legacy method works. One can learn this by just reading the legacy code itself, but writing tests is almost like rubbing my nose in it, and also documenting this temporary knowledge in separate tests. So this way I almost have no choice but to learn what the code is doing. I said temporary here, because I will refactor the heck out of the code and all my documentation and tests will be null and void for a significant part, except my knowledge will stay and allow me to be fresher on the refactoring.

Is that the real reason to write tests before refactor – to help me understand the code better? There's got to be another reason!

Please explain!

Note:

There is this post: Does it make sense to write tests for legacy code when there is no time for a complete refactoring? but it say "write tests before refactor", but doesn't say "why", or what to do if "writing tests" seems like "busy work that will be destroyed soon"

Best Answer

Refactoring is cleaning up a piece of code (e.g. improving the style, design, or algorithms), without changing (externally visible) behavior. You write tests not to make sure that the code before and after refactoring is the same, instead you write tests as an indicator that your application before and after refactoring behaves the same: The new code is compatible, and no new bugs were introduced.

Your primary concern should be to write unit tests for the public interface of your software. This interface should not change, so the tests (that are an automated check for this interface) shouldn't change either.

However, tests are also useful to locate errors, so it can make sense to write tests for private parts of your software as well. These tests are expected to change throughout refactoring. If you want to change an implementation detail (like the naming of a private function), you first update the tests to reflect your changed expectations, then make sure that the test fails (your expectations are not fulfilled), then you change the actual code and check that all tests pass again. At no point should the tests for the public interface start to fail.

This is more difficult when performing changes on a larger scale, e.g. redesigning multiple codependent parts. But there will be some kind of boundary, and at that boundary you'll be able to write tests.

Related Topic