Software process and the ilk

Saturday, December 16, 2006

How I Leared to Stop PrintFing and Love Testing

Output Woes
Back when I was a young buck cutting my teeth on silicon and spare cycles, I thought testing consisted of clever, or at least abundant, use of printing to console. Such debugging has the advantage only of being easy to implement. Test maintenance, relevance, repeatability, automatic checking: all these things were rumors, mythical weapons akin to Excalibur or a Vorpal Blade. Since then, I have learned of the simplicity of breakpoints, the grace of call stacks, the depth of profilers, and, more than anything, the breathtaking wonder of Automated Tests. Lo, and suddenly the mythical became the real, and gruesome beasts were slain.

Back to my young buck days. Many times I would write some code, test and debug it, and everything seemed beautiful. I would test inline, printing variables, conditions, paths, and I was good. My tests would even self proclaim their results: "Testing megasort: SUCCESS!" or "Testman saith: ABJECT FAILURE!" Certain of my overwhelming tests, I would blot out the test code so to keep my final source pristine and move on. Later I might notice a small change that needed to be made. Then, the day before (or worse, the day after) a deadline, I noticed that everything was broken. The small change had, of course, affected something I hadn't thought about, something I had had a test for, but had stopped running and removed from the system.

"No problem" I said in my sophomoric exuberance. The next time, I didn't blot the code out. I surrounded it with the ever imaginative "if(DEBUG)" statement. Define DEBUG once for the whole system, and now I could test everything. Of course, I still had to inspect everything, and the tests remained in my code...

Automated Vigor
Thousands of iterations later I might have come up with Automated Tests. I didn't, of course; people much wiser than me forged this weapon, but I gained the benefit.

An Automated Test is a test that's run (gasp) automatically, usually at build time. Automated Test frameworks abstract tests away from the code, so we can keep our code sharp and unblemished from the smelting fires of test code. The tests report to the frameworks, rather than to standard out or an error file, and the framework, not the human, does the checking. The framework only tells us when something goes wrong. Since the tests are always run, we get notified when any tested code breaks, no matter the age. They are a powerful tool of our craft.

I began using Automated Tests when I took over the build process for a medium-sized java program. Whenever the build broke, I needed to show the developer exactly how it broke. This is when I discovered how often the same bug would pop up over and over. I would show a developer his error, and the next week, it would reappear. After introduced Automated Tests (in the form of JUnit) into the code, my life started easing up. I no longer had to recreate a test over and over for the same bug - I could simply point to the test. Then we integrated Automated Tests into our IDE, and things really started looking up. Slowly the development team started running the tests before checking in code, and gradually our productivity increased, our code was cleaner, our bugs fewer.

Automated tests are really just another extension of a Refactoring mindset: if something is worth coding multiple times, it's only worth coding once. If we're going to do it several times, we're better off just doing it once and calling it several times.

Of course, there are dark sides to anything. Automated Tests are code, and code must be maintained. It's possible to spend all our time writing tests that are repeated, or unnecessary. Typically Automated Tests are not deliverable, which might throw off statistics for line-accountants. Occasionally an ill-conceived test can send developers scampering off after ghost-bugs. However, treated with respect they deserve, Automated Tests can help us slaughter bugs and guard against our tendency to resurrect them.