Should You Write Tests for Old Code?
If you know Betteridge's Law, you might know there this is going
Maintaining old code is one of the most common tasks of a profissional Software Engineer. Sometimes old means good, refined. Other times - depressingly often - it just means shitty. Complain as we may, we still need to deal with it.
In dire circumstances when development velocity comes to a crawl, someone will suggest: what if we spend a sprint or two writing unit tests?
The Case for Writing Unit Tests for Old Code
When changing old code, you will often find that fixing a bug breaks two. The breakages come from the poor structure of the code as well as a lack of understanding by the maintainers. Often, they will be fixing symptoms rather than root causes. Software is just damn hard, so it can happen to the best of us.
Unit tests can help by serving as guardrails. As you fumble your way through the code trying to develop a mental model of it, you will try things and get immediate feedback on whether they are right or wrong. This fast-learning cycle will in a brief time allow you to crank out correct code much faster than if you had not written tests.
The Reveal - Should You Write Tests for Old Code?
The case above is airtight, or is it?
While it seems right, it is in fact wrong to stop feature work to right unit tests. To understand why we must first understand what unit tests are for.
First, Unit Tests Help Debug Code When It’s First Written
When you write new code, it will be much easier to take the bugs out (debug it) if you write tests. The reason is simple: writing tests forces you to consider many cases formally. You’d be surprised how sloppy your understanding of something is until you formally write it down1. The old adage applies, “I write to know what I think”.
Second, Unit Tests Ensure Code does not Break as it Changes
Simple really: change code, run tests, tests break, give up and try something different.
Third, They Help Programmers Understand the Code
Not only tests shorten the learning cycle, as I mentioned before, they are also a formal statement of the intended effects of the code. Reading tests is thus an effective aid in understanding code2.
The Problem with Stopping Feature Work to Write Tests
From the three reasons above, the first and second don’t apply to old code:
You are not first writing the code. It has already been debugged! While writing tests would have been the most effective way to do it, that ship has sailed. And it was done the old fashion way - having customers report bugs and complain the software is crap - like God intended.
You are also not changing the code. It is sitting still while you write tests for it. Exactly zero customer value is generated when you stop to write tests.
Only the third reason remains: change tests to learn the code. The problem with this approach is that, without real motivating changes, you don’t know what tests to write. Likely you’ll spray and pray, like doing all files alphabetically, or you will try to guess the most important parts to test. But if the goal is to understand the code, you will not have a good understanding when you start, so your guesses will be mediocre at best.
I’ll finally assume that since you are working in a company that has a codebase with no tests, you don’t have that much political capital to begin with. So, spending a lot of it to add a bunch of questionable tests, while you add no user value might be unwise. It’s likely the project will be declared a failure3, which will further decrease team morale. It might even cement the fact that “tests don’t work for us”, an ironic tragedy.
What you Should do Instead
You should start writing tests immediately, but always motivated by organic code changes4. Those changes will naturally cover the most important parts of the code - the ones that change more often or are more brittle. Real changes will also keep people motivated with a feeling that progress is being made. Finally, all the advantages of writing tests I mentioned before will start to accrue immediately.
Notice that since tests were not a part of the culture before, they can’t just be decreed. People likely don’t even know how to write them, or think they are bullshit. Here’s few strategies I’d try to effect real culture change.
Pair program with folks to write the first tests. This should be with the most skilled people, as you are developing best practices and test tooling as you go. Inevitably people will take these tests as the standard to aspire to and copy.
Mob program test writing to quickly teach everyone how to do it, and to signal: yes, this is serious enough we are putting you all in a room because of it.
Select a couple of people who are interested and skilled and recruit them to the cause. Stroke their egos: “You are one of the good ones around here. Lack of tests is our biggest problem, and you are the one I trust to fix this.” These champions can then perform the activities above the same way you do, multiplying and reinforcing the message. Remember to highlight their role and complement them in public, not only to make them proud and more applied to the task, but also to raise their prestige and signal to others, they should copy them.
In Summary
You are 100% right that your codebase is shit for not having tests and that needs to change immediately. Nevertheless, you need to plan the change in cold blood. Don’t decree “test writing sprints”. Instead ask for all new or changed code to include tests and take people by the hand while they learn to do it.
Do this, and you will be lauded as the savior of the codebase, not the perfectionist who proposed yet another failed project.
This effect has a fancy name: The Illusion of Explanatory Depth. A simple demonstration: try to draw a bike from memory and notice that though you think it should be trivial, you can’t really do it well.
Ideally code should be obvious, so tests should not be needed to know what it’s doing. Unfortunately, the average programmer is incapable of writing obvious code. They could still get by writing good comments, but we don’t get anymore. Clean Code taught a whole generation of programmers you should never write comments. A tragedy really, but a topic for another day.
It won’t bring quick improvements, and whatever political will was mustered will quickly dissipate, due to lack of perceived progress.
I would even institute a hard rule: no new or changed code without tests. After the culture sticks you can remove the rule and let common sense reign.