Nearly forty years into the coding game, I’ve had more embarrassing moments than I’d care to remember. Accidentally deleted the entire filesystem? Check. Re-implemented a free library badly? Check. Wasted months on nowhere plans for nobody? Check, check, and check. But one self-inflicted punch to the gut was so painful that I haven’t so much as whispered about it in fifteen years. Ready to take some pleasure as I expose it for the first time? Read on.
I was working in a tiny shop as the sole developer of my company’s server-side software. I didn’t design this codebase. In fact, I spent most of my time trying to make sense of it, especially one huge function that I called “the blender” because it took all kinds of apparently unrelated things, mashed them together, and sent chunks of puree flying off in all directions. Of course, it was the place where most of the business logic lived and the only surviving documentation of long forgotten rules, so I couldn’t simply ignore it. I decided that my best approach to understanding the blender was to decompose it, systematically breaking the huge function into smaller functions. A well-trained programmer would immediately recognize this exercise as a series of “extract method” refactorings. Not only was I not a well-trained programmer, but years of being the biggest fish in small ponds had bred dangerous delusions of grandeur.
My supervisor kept asking me whether I was unit testing as I chopped the blender into comprehensible chunks. Sure, I was unit testing. I kept executing the function and observing its operations. Didn’t I? After a few days of this, he asked to see my unit tests. See my unit tests? You mean a unit test is an artifact that you can show somebody?
The capper: earlier in my career, I had been a “software quality assurance engineer” for more than four years. Now, I was exposed as so incompetent that I didn’t know what a unit test was. I’d like to follow that sentence with “so I learned quickly” but that would be a lie. The truth is that I don’t remember what I did next. Call it psychological repression. Pain will do that.
I do know that I’m not the only coder who associates unit tests with pain. Approach a group of developers, ask them about their tests, count to ten slowly, and find yourself alone in the room. That’s not true of all coders, of course. A small minority found religion and will say things like “code without tests is legacy by definition” (Michael Feathers) or “I’m a good developer because I’m a great tester” (Mike Hoswell) but the majority (as far as I’ve observed) will find reasons to avoid testing or to do a perfunctory job of it.
The most cited reasons not to put much effort into building unit tests? First, it’s difficult. Second, it’s time consuming. Both points are undeniably true, particularly when you’re just getting started. You could make the same points about any discipline. Try picking up an unfamiliar musical instrument and producing sounds worth hearing without devoting hundreds of hours. After I finally saw the light and vowed to make unit testing part of my everyday practice, it took nearly a year before the red-green-refactor cycle of test-driven development stopped feeling completely alien. I still have moments where I find it nearly impossible to resist the temptation to skip a particularly difficult or tedious test but I’ve learned that I can’t live without solid test coverage. My tests advise me about what my code is expected to accomplish and they warn me when I’ve accidentally broken something. Take my tests away and I literally don’t know what I’m building, much less whether my changes are doing more good or harm. In those dark years before I had tests to provide empirical evidence, I thought I knew but was only guessing.
As part of my journey, I developed two habits to reduce the difficulty and tedium. First, I learned to make my tests fun and sometimes even a little silly where possible. If you’re going to construct fake objects and arbitrary data anyway, why not fill them with jokes, so long as they don’t make the tests difficult for someone else to understand? Second, I discovered that I was using the same testing patterns repeatedly. There are only so many ways that you can spy on a function call.
Unfortunately, Python’s module structure does not make it easy to reuse components between tests in the same project. About three years ago, I worked around this limitation by creating a new project just for the testing components. As the testing project evolved, its centerpiece became a pure Python dependency injector which my colleagues and I found increasingly helpful. Our employer agreed to release it as the open source Twin Sister project. If you’re curious, you can find the source code at https://github.com/CyberGRX/twin-sister or simply install the Python package from PyPi: https://pypi.org/project/twin-sister/ .
I may never redeem myself for being the “software quality assurance engineer” who didn’t know what a unit test was or for finally picking up the unit testing habit late in my career, but hope to make some progress by paving the road so the journey will not be so difficult for others.
As a child in the early 1980s, Mike Duskis discovered that hacking video games was more fun than playing them. Then he hacked his way into a software development career spanning industries from children’s entertainment to safety-critical medical devices. He is currently Test Manager at CyberGRX in Denver, Colorado, USA.