Using unit tests as requirements when refactoring

September 16, 2010 – 6:07 pm Tags: , ,

What should you do to make sure new code works properly when you’re refactoring old code? I asked myself this question recently, when I needed to refactor a big bunch of procedural PHP code into a neat, testable, OOP-style interface.

The conclusion I came into is that you should write unit tests – not to test the old code, but as requirements for the new code.

Tests as requirements

When you are coding a new feature, you usually have some simple requirements.

Send an email, put something in the database, and so forth.

When the codebase has grown without much limitation, as was the case for my task, the code becomes a mess of different requirements. For example, there could be a 300 line function which performs 10 different things – Good luck figuring that out!

A solution to this is of course to go through the code and write down all the things the code does, but why not take this to the next level and write the requirements down as tests?

Essentially what I’m suggesting is “reverse TDD” – You already have the code, now you write tests. Except I think that’s just called writing tests, but “reverse TDD” sounds like a proper term, right? ;)

What if the old code is untestable

As I mentioned, the old code could be written in a fashion which is difficult to test.

In this case, you can just ignore writing any content for the test. Simply write test stubs which have a descriptive name (all tests should always have a good name) and make sure they are marked incomplete or fail. You can fill in test-code inside the stubs once you begin writing new code.

Why this is better than just writing down what the code does

Even though this approach requires some more effort than just writing the requirements down to, say, a piece of paper, I think it’s worth to do it this way.

When you have written down the requirements as tests, you can easily see what features you’re still missing, which of them you have already implemented and so on.

When you are done, you already end up with code that has already been tested. Benefits everywhere!

Example

Let’s say you have some code which adds a private message to a user’s inbox. It also sends an email to notify the user of a new private message.

How would you distill these requirements into test cases?

You could, for example, have tests such as these:

  • testPrivateMessageAddedToInbox
  • testEmailIsSentWhenPrivateMessageIsAdded

Now you have to requirements that are quite clear. Now you can begin implementing the tests, and begin refactoring the code.

When working on the code, you may find that your tests aren’t precise enough. This is okay and quite normal – just add some more tests to make sure all cases are secured.

You may also find that you need split the refactored class into more than one. In this case, you can simply move your relevant test cases into the new class’ tests.

Conclusion

You can make refactoring old code much easier when you have a list of things to do. Unit tests are a quite natural way to approach this: You have a clear list of things, which even show you whether or not it works/is done.

Write a test per each feature the old code does. It doesn’t necessarily have to test the old code, just act as a reminder and a placeholder for test code for the to-be-written replacement code. Later you will thank yourself for doing it.

You may also be interested in:
How to write code that is easy to test

Share this:
  1. 4 Responses to “Using unit tests as requirements when refactoring”

  2. I’ve done this a few times in the past when I needed to rewrite a system and it really worked out well. I started by just writing tests to make sure I knew what the old code was doing, and that it was doing it properly. (I found some bugs every time!)

    Then I started on the new system. As soon as enough of it was there to get the tests failing (which isn’t much!) I ported the tests to how they should be for the new system. Then I just wrote the system and watched the tests start passing. When I got them all working, it was time for some user testing.

    It went much, much better than it would have if I hadn’t done the tests.

    By WC on Sep 16, 2010

  3. I always appreciate efforts to introduce some TDD into the PHP world. Tests are the best form of executable specification: while a document is imprecise and needs a human to be effective, a test can be both read and automatically run.

    By Giorgio Sironi on Sep 16, 2010

  4. Instead of “reverse TDD” how about “Development Driven Testing” or DDT :) A nicely reversed acronym to depict a reversed process!

    Jim.

    By Jim O'Halloran on Sep 17, 2010

  5. WC, surprisingly in my case the code was somehow bug-free even though it seemed to me very complex. It was probably written in one or two sittings by the same guys though so they probably had the advantage in remembering it by heart.

    Giorgio, thanks for putting it so smart soundingly :D

    Jim, I hope that has nothing to do with the toxic properties of a certain other DDT ;)

    By Jani Hartikainen on Sep 17, 2010

Post a Comment

You can use some HTML (a, em, strong, etc.). If you want to post code, use <pre lang="PHP">code here</pre> (you can replace PHP with the language you are posting)