Why do programmers hate writing unit tests? Why do they hate even more writing unit tests before coding? You don’t have to answer. I’ve already heard the excuses. These are rhetorical questions. I have a theory, however, what the real reasons are.
Most software developers have never seriously tried test-first. Or if they tried it, they did not do so in a supportive environment where they could learn what they were doing. But more likely the former. So they come up with excuses: “We don’t have time to write unit tests.” Or: “Unit tests can’t bullet-proof your code.” They’re reacting out of fear, not out of empowerment, trying to find reasons for being miserable, rather than making their lives better. The neat thing is that occasionally you’ll find one of these developers who, when you face him with the facts, nearly trips over his own feed backpedalling. That’s fun to watch. And he does this all without ever taking back his opinion of test-first. That’s wicked philosophical.
As Kent Beck explains in Test Driven Development By Example, test-first comprises three steps:
- Red — Write a test that expresses how you’ll use the code and what you need it to do. This test will fail, producing a red bar on many UI’s.
- Green — Write enough code to get the test to pass, but no more. If you need more code, for example, to check for errors, first write another test to demonstrate that feature. For now, just write enough code to get the test to pass.
- Refactor — Clean up the code to remove redundancy and improve the design. Then re-run the tests to make sure you didn’t break anything.
Repeat these until you’re done. It’s an incredibly simple procedure. So why do developers fear it? Because it requires them to make a fundamental paradigm shift in the way they develop software.
How do you solve a software problem? How do they teach you to handle it in school? What’s the first thing you do? You think about how to solve it. You ask, “What code will I write to generate a solution?” But that’s backward. The first thing you should be doing— In fact, this is what they say in school, too, though in my experience it’s paid more lip-service than actual service— The first thing you ask is not “What code will I write?” The first thing you ask is “How will I know that I’ve solved the problem?”
We’re taught to assume we already know how to tell whether our solution works. It’s a non-question. Like indecency, we’ll know it when we see it. We believe we don’t actually need to think, before we write our code, about what it needs to do. This belief is so deeply ingrained, it’s difficult for most of us to change. Of course, for a trouble-maker like me, the change was nothing more than a sensible experiment that worked.
For anyone who can make that leap, here are some of the benefits you’ll experience. I’ve lived all of these. But don’t take my word for it. Try it yourself, and see.
Unit tests prove that your code actually works. That means you have fewer bugs. No, unit tests can’t replace system and acceptance testing. But they do supplement it. The fewer bugs that make it to SQA, the better.
You get a low-level regression-test suite. You can go back at any time and see not only what broke but where the bug is. Many teams run the unit test suite as part of the nightly build. It’s a low-effort way to catch bugs before the build goes off to SQA.
You can improve the design without breaking it. This is actually part of step 3 above, the refactoring step. So test-first code usually doesn’t need to be refactored. I have worked on some systems that were so screwed up, like a psychotic individual, you just couldn’t untangle them. Having unit tests in place, you can do powerful refactorings that can untangle the most challenging of system psychoses.
It’s more fun to code with them than without. You know what your code needs to do. Then you make it do it. Even if you don’t have a working system, you can see your code actually run and actually work. You get that great “I’ve done it!” feeling. Now repeat every minute or so. Just try test-first if you want to be high on endorphins, proud about your work, and motivated to do more.
They demonstrate concrete progress. You don’t have to wait a month for all the pieces of the system to come together. You can show progress even without a working system. Not only can you say you’ve written the code, you can actually demonstrate success. Of course, this is another distinction that traditional programming teaches us to ignore. “Done” doesn’t mean you’ve written the code and checked it in. “Done” means the code actually runs in the system without bugs. Running unit tests is a step closer to the latter.
Unit tests are a form of sample code. We all encounter library functions and classes we don’t know how to use. And one of the first places we go is the sample code. Sample code is documentation. But we don’t usually have samples for internal code. So we’re left sifting through the source or through the rest of the system. Because Bob, the guy who wrote that code, is no longer with the company, so we can’t actually ask him how it’s supposed to work. But unit tests are documentation. So when you can’t remember how to use class Foo, read the unit tests to find out.
Test-first forces you to plan before you code. Writing the test first forces you to think through your design and what it must accomplish before you write the code. This not only keeps you focused, it makes for better designs.
Test-first reduces the cost of bugs. Bugs detected earlier are easier to fix. Bugs detected later are usually the result of many changes, and we don’t know which one caused the bug. So first we have to hunt for and find the bug. Then we have to refresh our memories on how the code is supposed to work, because we haven’t seen it for months. Then finally we understand enough to propose a solution. Anything that reduces the time between when we code the bug and when we detect it seems like a obvious win. We consider ourselves lucky to find out about bugs within a few days, before the code is shipped to SQA or to customers. But how about catching them within a few minutes? That’s what test-first accomplishes with the bugs it catches.
It’s even better than code inspections. Code inspections, they say, are better than testing, because using them to detect and fix bugs is cheaper than testing. After the code ships, it’s much more expensive to fix the bugs. The earlier we can detect and fix bugs, the easier and cheaper and better. That’s the advantage of having code reviews: Code inspections catch more bugs within a few days, rather than a few months. But test-first catches some bugs within a few minutes instead of a few days. It is even cheaper than code inspections.
It virtually eliminates coder’s block. Ever wonder what statement to write next? Like writer’s block, coder’s block can be a real problem. But test-first systematizes the structured part of coding, allowing you to concentrate on the creative part. You may get stuck on how to test the next bit or how to make the test pass, but you’ll never be left puzzling over where to go next. In fact, usually you’re left with the opposite problem: You know you need to take a break before you burn out, but you’re on a roll and don’t want to stop.
Unit tests make better designs. Testing a piece of code forces you to define what that code is responsible for. If you can do this easily, that means the code’s responsibility is well-defined and therefore that it has high cohesion. And if you can unit-test your code, that means you can bind it as easily to the test as to the rest of the system. Therefore, it has loose coupling to the pieces around it. High cohesion and loose coupling is the definition of good, maintainable design. Code that is easy to unit-test is also easy to maintain.
It’s faster than writing code without tests! Or to put it another way, skipping unit tests is faster, unless you actually need the code to work. Most of the effort we spend on code, we spend fixing it after we’ve checked it in to the source-code repository. But test-first eliminates much of that waste by allowing us to get more of it right to start with and by making bugs easier to fix.
Even with all this, many software developers continue to hold on to their old ways. If you are a process evangelist in your organization, you may find yourself up against some of them. I wish you the best. Just remember, people don’t buy into something just because they want it or because it sounds good. They only buy in when they’re desperate, when they need it so badly that they can taste it. I hope you can pull something from this list to help you in your endeavor.
However, if you are one of the former, one of those curmudgeon coders who would rather be right than to design good software… Well, you truly have my pity.
Why Agile Software Development Techniques Work: Improved Feedback
How TDD improves development speed and is very cost effective
To obtain good code, writing tests and code is faster then code alone