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
Published on Java Code Geeks with permission by Ashley Frieze, partner at our JCG program. See the original article here: The Unit Test Time Machine Opinions expressed by Java Code Geeks contributors are their own. |