Tuesday, March 10, 2009

Why we do it: Unit Testing

What started me on this blog on such an elementary concept? I could list a couple reasons. There are those that don't unit test. To them, unit testing is an annoyance. They're experienced so they know exactly what the design and subsequent code will be. Unit testing just slows them down. I might comment that their defect rate is much higher than my own or that their code is far harder to modify or work with. These things don't matter. What matters is that when I face this resistance I need to be ready to respond with all the facts that negates any ill-informed opinions.

The other reason I'm on this is that I've been listening to too many podcasts. Notably some from Bob Martin (with his craftsmanship movement) and Scott Bellware's less-than-complimentary view on recent alt.net activity. They reasonably argue that developers latch onto the idea of using unit tests or practicing SOLID without ever knowing what benefit it is to them. They can't quickly tell you why they use it. In effect they're stagnating an otherwise progressive movement like alt.net by no longer questioning the practices that gave them the awesome methodologies and tools that they're using.

As a final note, lots of the benefits of unit testing are sometimes attributed to TDD. Recognize that TDD's primary goal is design and it is the beneficiary of unit testing. From what I read, BDD was born from this fundamental misunderstanding. This writing also assumes that we understand what good test writing is. When you change code it shouldn't make 10% of your tests break. That's a code smell. If you find yourself in such a situation then I recommend reading the XUnit Test Patterns book.

Without further adieu...

Unit tests are documentation

They document your API and demonstrate its usage. Test assertions are telling clients exactly how to specify the inputs to get the desired outputs. Invalid and erroneous use cases are outlined. Best practices for your code are emphasized. For these reasons, you may see developers (like myself) that will forgive violating DRY in order to make the test more story-like and readable. You want anyone to read the script and "get it".

Unit tests are better than documentation. Code comments, UML diagrams, design docs, etc. go stale within minutes of the ink drying. They aren't reliable for anything but the most static systems. That's not to say that they don't have their place but ,for the sake of development, they're best expressed in white board sessions and reflected in the code.

Unit tests are security

Good unit tests execute in fractions of a second and running the whole test suite should take a few seconds at most. If your unit testing suite executes on the order of minutes then they aren't unit tests.

They are your safety net for regression. Introducing change to the system will raise red flags anywhere that has been impacted. This immediate feedback loop allows us to fix problems before they go out the door as defects. It also illustrates defects in design. Making a simple change that implodes half of your test suite indicates that the recipient of the change is a dependency attractor.

I'll add the obligatory mention of ease of refactoring. The added security of unit tests allows you to confidently refactor your code with little fear of breaking the system.

Unit tests preclude debugging

At one point in my life I thought it was the best thing ever to be able to walk through my code, examine values, change them, simulate branching logic and so forth. No code could be a mystery to me because I could interrogate it in the watch window and make it tell its secrets. I'd be lying if I said that it didn't save my ass on more than one occasion.

Flash forward to today. I hate debugging. Debugging is a hassle. Someone has a problem in code then I have to go do a source control update, get the latest dependencies, do a build, work through peripheral issues in getting my integration environment up to snuff, somehow ham-bone data in the DB to recreate the issue BEFORE I can even begin to execute the debugger. Then, after I run through the debugger once, I have to do a DB restore or some other voodoo magic to restore my integration environment so that I may try again.

Imagine my shock and surprise when the problem that warranted the debugging session was as pathetic and minor as are most defects I've worked through. In fact, I can't remember the last time I found a defect that was anything more than an uninitialized value or an unexpected branching. It's almost heart breaking to make this discovery as you'd hope it would be some mythical code beast that needs slaying so then at least traveling minstrels and bards will sing songs of your valor and courage (Monty Python just sprung to mind).

Unit testing isolates the units of the system (duh). Recreating and driving out defects can be done without firing up any debuggers. Some TDD practitioners will profess that they never have to use a debugger, ever. The tests themselves provide the debugging. I can't make such a boast. The point is that you will be less involved with debugging because you will have appropriately addressed the conditions that create defects and you can easily hop into and test the code that is throwing the error. Debugging becomes a "break glass in case of emergency" type utility. You only need it in extreme circumstances.

One thing that I can boast is that integration or functional tests seem to be a formality now. I can almost always guarantee that my code works right out of the box before I deploy it to an integrated environment. It's a very satisfying thing to me and to clients of my code.

AUTHOR'S NOTE: I kind of got bored with this post halfway through it so if you're not feeling the love then neither was I. It's not you, baby, it's me. I'm just getting this out there since I've spent enough time looking at it in my posting queue.

No comments: