But even with all this activity and talking around testing, I still find myself troubled by whether I'm using them "right." As simple as testing seems at first glance, it’s a big subject. There are all these contentious arguments about how it should be done. From what kinds of tests we should write, to when we should write them, to exactly how they should be written. It's easy to be overwhelmed by these details and lose sight of what is important, causing bad decisions to be made, or time to be wasted on issues that might not be so important in the big scheme of things.
A clear understanding of the motivations of a technique, like testing, can keep the focus on what matters and can guide a good application of the technique to a specific scenario. And this can shed some light on why other people's opinions and practice might differ due to their different context.
In the case of testing, I think we can summarize the motivations of the technique by asking a very simple question: why do we write tests?
Why do we write tests?To start with, I should be very clear that I'm talking about developers here. I am not talking about why a separate QA department might write tests. If you work in an environment with a separate test-writing QA department, it may change some of the algebra of how you approach testing, and certainly the QA people themselves will have very different motivations from the developers. But none of that will change the major motivations and benefits for why developers write tests. Which serves as a great example of why understanding the underpinning motivations is so important!
OK great! So why do developers write tests?
Because. Because people say we should. And for a lot of people, I think “because” is the only reason they’ve ever been given. But there are reasons, and chief among them is this:
Testing, done well, makes software development take less time, thereby reducing the costs of development. But this is really surprising, isn't it? How could adding the work of testing possibly make development take less time? Especially given that the #1 excuse I hear for why people don't test is because they think they don't have time for it! Which does make sense, after all writing tests necessarily means writing more code. By some estimates up to 2 lines of test code for every 1 line of production code! That is a lot more code! And even given the fact that lines of code is a horrible metric, that still represents a significant amount of extra work."The true purpose of testing, just like the true purpose of design, is to reduce costs."1
How could testing reduce development cost?There are, in fact, a lot of productivity benefits that developers derive from having and writing tests. I want to just quickly summarize a few of them before we get to the really big one. The first is this: "Fixing bugs early always lowers costs."1 When a developer is "in the zone" with some code, it takes significantly less time to track down and fix issues. So if tests can help us find issues when we're in the zone, that's a great time savings. And, of course, finding a bug before it gets into production is a HUGE time savings to the organization as a whole.
The next reason is really simple. Automated tests can save you a lot of time and be more effective than manual tests alone. They don't replace manual tests, but automated tests can take some of the regression testing burden off of the manual testers, saving the whole team time. Plus automated tests can be run faster and more often, providing feedback more quickly. And you can also write automated tests for things that may be impossible to test manually, like error conditions, helping you find bugs earlier.
Tests can also serve as documentation of code, helping us to understand existing code faster and to work with it more effectively. And along these same lines, tests can be used like an experiment to prove how some code actually works, saving on time spent mentally reasoning about how it should work and cutting straight to the chase.
Another big and commonly cited reason is that writing tests helps cut down on the amount of time we spend debugging. Debugging is awful. Once you have to fire up a debugger there is absolutely no way to know how long you'll be at it. But people who write tests have experienced how much less time needs to be spent in the debugger. And the time that does need to be spent seems lessened partly because tests localize the area of code that might have the problem.
All of these are ways that writing and having tests helps lower the costs of development. But probably the most important way tests help lower the cost of development is this: confidence. You might think we're talking about confidence in the correctness or quality of code, and that's part of it, but it's not the biggest part. The biggest part is actually that we have confidence to change the code! Why would this be so important?
Software rotSoftware rot is the phenomenon where by code gets worse as time goes on. If you've worked for any kind of duration on a project you've doubtless experienced it. But why does our software rot like this? The simple answer is because we so often fail to refactor.
Refactoring code is important, and yes, I know that this is not earth shattering news. But I feel like we often think of refactoring as this thing we do later. Like, once things get bad enough, we can always come back and refactor. But that's not right. That kind of thinking just makes software rot faster!
When we have big changes to make, the need to refactor is obvious. But it's when the changes we're making are small that we really get into trouble! The small changes are the ones that we shoe horn into the code. "I'll just add an if statement here" we think, "It makes it work, what's the harm?"
What's the harm? Software rot! That is software rot happening right before your eyes. Those seemingly small changes have a tendency to compound, rendering the code unintelligible. And in addition, surprisingly often, the small changes represent a major shift in the appropriate design. But even if we are aware of this, we ignore it. Why?
We ignore it because we're trying to get the feature done so we can move on to the next thing. We're trying to be productive! Little realizing we are shooting ourselves in the foot. But we also ignore it because we think it's safer to change the code as little as possible. We have learned to avoid making "invasive" changes to code because we've seen even the simplest of changes break stuff. We've internalized this fear of change. And that's why we shoe horn in changes, but of course this inevitably leads to software rot!
And rotten code just makes us more afraid, which leads us to shoe horn in more changes, leading to even more rotten code, and around and around we go!
Which finally brings us back to confidence. What we need to break out of this destructive loop is the confidence to make those "invasive" code and design changes. Because if we don't have that confidence we wont make the kind of code changes that we absolutely need to make to prevent our code from rotting!
So, why do we write tests?Because tests are the best and cheapest tool that we have to manage fear and replace it with confidence, so that we can and will refactor, so that our code doesn't rot, so that we can build software faster, indefinitely.
When we have tests, we know we can change code. Because if something goes wrong, the tests will tell us right away. We can refactor constantly, working to keep the code as simple and communicative as possible at all times. If something goes wrong, the tests will tell us right away. We can shoe horn in changes, but then we can take that next critical step of refactoring the design to reflect it's new requirements! If something goes wrong, the tests will tell us right away. We can experiment with new names or new class structures and see what works best. If something goes wrong, the tests will tell us right away. We can truly live the Boy Scout rule, leaving code cleaner than it was when we found it. And if something goes wrong, the tests will tell us right away.
And all of this will mean that our code is as intention revealing and simple as possible. And in a code base like that, we can quickly respond to new feature requests and surprising bugs. We can take on enhancements and incorporate new ideas that with a rotted code base we would probably have had to say no to. And that means not only are we delivering features and enhancements faster, we're building a better product too! This is the promise of tests, and it's the ultimate motivation for why developers write tests.
1. Practical Object-Oriented Design in Ruby