When I work on under tested projects, I waste an enormous amount of time trying to figure out how to change the code safely.
Most projects make it straightforward to get a copy of their source code, get the code’s build time dependencies, and build executables from the source code. Then the wheels start to come off. What do I execute? How do I execute it?
If it’s a user application, I might know how to get started and explore the user interface.
If it’s a service, what do I do with it once it’s executing?
If it does a lot of different things, how do I make it execute the part of the source code I’m interested in? How much overhead is there to get that part of the source code to execute?
The time I spend asking myself these questions and searching for answers is waste.
Sure, I learn something by getting stuck and finding ways to unstick myself. But I’d much rather get stuck on interesting problems rather than mundane ones.
It should take near zero mental energy to:
- Get the source code.
- Get the build time and run time dependencies.
- Build executables.
- Start the executables.
- Exercise each executables.
If we make mundane prerequisite steps for development hard, we delay and reduce potentially valuable contributions from new and existing developers alike.
Too often we only make it possible to explore projects from the top down through the user interface. If the project is a service with no user interface, it’s even worse. Before you can even begin exploring, you have to understand the service’s API. The easier a project makes it to execute every line of its source code, the more developers can explore the project from the bottom up.
In a better world, we should also strive to make it straightforward to add some logging to any part of the source code or set a break point on any line, and observe some executable emit the corresponding output or drop into a debugger. Said another way, it should be trivial to execute every line of source code in the project.
I assume that I can execute every line of your project’s source code through the user interface. If not, the code is dead. Go remove it. I’ll wait. Done? Feels good, right?
But the average case of executing a given line of code through the user interface involves a ton of overhead. If that’s the only way to exercise some code we want to change, changing that code is expensive.
Tests are the alternative.
On under tested projects, the waste of developer time spent trying to figure out how to exercise any given area of code is enormous.
Once you’re familiar with a project, it’s easy to rationalize not writing tests. You’re making faster progress in the short run by avoiding the overhead of figuring out how to write tests in general, or how to write them in your current project.
But programming without tests is incredibly short sighted. I’d guess the short run is measured in hours if you’re already skilled with testing practices and the testing tools in your development environment. If you’re skilled with testing practices in general, but not the tools in your current environment, I’d measure the payoff in days. Even if you’re learning testing practices for the first time, I’d expect you’ll go faster, on balance, within weeks, if you invest in learning.1
Test avoidance is a bad bargain. Your team pays the price every time it adds a new member and every time an existing member needs to learn a new area of the code. And you don’t have to think of anyone other than your future self to justify the investment in testing. You pay the price of past test avoidance yourself every time you return to an old area of the code that you have to relearn, and every time you pause, even momentarily, to consider whether a small design improvement will cause a regression in some other behavior.2
2 Your tests might not catch regressions. But having tests to read will increase your understanding of the code under test and your confidence that you can make a change without breaking any assumptions that calling code makes.