The Unit Test Time Machine

What do you call refactoring without unit tests?

Anyone?

Yes. It’s a trick question isn’t it?

Refactoring without unit tests is not called refactoring. It’s called changing the code. It may even be called breaking the code.

Adding Tests As You Go

When you need to change untested code, then it’s useful to add unit tests. There are essentially two types you might add:

  • An obvious use case – where you’ve understood the code and can see a use case it can handle and it’s nice and simple
  • Characterising – where you’ve no idea what the code’s meant to do, but you can run some data through it, capture the output, and then write an assertion in a unit test that says the output from the future times I run this should be whatever this is…

Unfortunately, with refactoring of complex legacy code, the latter is the best tool you have. Though once you’ve nailed down the black box everything test, you can dissect the code a bit, adding use-case driven tests and eventually bring it under control.

What if I’ve Changed it Without Tests?

There are some refactorings you can do, especially with the available tools in your IDE, which don’t seem to justify writing tests first.

And after a couple of hours, you may wonder – Yes, but have I broken it?. And there you are on your branch with changed code.. it may pass all the current unit tests, but does it do the same as your master branch version? We need to Step Back in Time a moment.

Here’s where git can rescue you (other source control is available, but grow up: it’s 2020, use git).

  • Keep your branch nice and safe
  • Go back to `master`
  • Create a new feature branch with the missing unit test(s)
  • Get those building and then merged
  • Go back to your refactor branch, and then rebase

Even if you’ve changed the interfaces slightly, this technique will help, because after the rebase, you’ll only have a little test code to tweak… or at least a predictable amount.

If the test that you slid into master passes on your feature branch, then congratulations. If not, then you’ll wish you’d refactored differently… though you may be able to debug it and fix forwards.

If you gain new doubts, you can repeat the process and slide more tests into the head of master and rebase your refactoring against them.

What if it’s VERY Late?

This is where git proves to be Linus Torvalds’ time machine. The process I’ve described could be applied to any commit in the git history. Checkout a commit before you did the majority of your refactoring, add a unit test to a feature branch taken from there. Get it to go green. Then cherry-pick that unit test into a later feature branch to see what it does in the code of the present.

TL;DR

  • You need tests to support refactoring
  • Add them as you go
  • Or retrospectively add them to earlier versions of the code, using `git` to rewind
  • Then bring the tests that pass in the original code together with your latest refactoring

3 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s