Something interesting occurred to me the other day when I posted sample code for a DXCore plugin that I created. In the code that I uploaded, I added a unit test project with a few unit tests as a matter of course. Apparently, the process of unit testing has become so ingrained in me that I didn’t think anything of it until later. This caused me to reflect a bit on my relationship, as a developer, to unit testing.
I’ve worked in settings where unit tests have been everything from mandated to tolerated to scoffed at and discouraged. And I’ve found that I do some form of unit testing in all of these environments. In environments that require unit testing, I simply include them the way everyone else does, conforming to standards. In environments that discourage unit testing, I implement them and keep them to myself. In that case, the tests aren’t always conventional or pretty, but I always find some way to automate verification. To me, this is in my blood as a programmer. I want to automate everything and I don’t see why verifying my code’s functionality should be any different.
But I realize that it’s now something beyond just a desire to automate, tinker, and take pride in my work. The title of this post is tongue-in-cheek, but also appropriate. When I write code and don’t do some verification of it, I start to feel edgy and off of my game. It’s hard for me to understand how people can function without unit testing their code. How do they know it works? How do they know they’ve handled edge cases and bad input to methods? How do they feel comfortable building new classes that depend on the correct functioning of the ones that came first? And, most importantly, how do they know they’re not running in place when adding functionality — breaking an existing requirement for each new one they satisfy?
I exhibit some of the classic signs of an addict. I become paranoid and discombobulated without the thing upon which I depend. I’ll go to various lengths to test my code, even if it’s not factored into the implementation time (work longer hours, create external structure, find free tools, etc.). I reach for it without really thinking about it, as evidenced by the unit tests in my uploaded code.
But I suppose the metaphor ends there. Because unlike the vices about which recovering addicts might speak this way — drugs, alcohol, gambling, etc. — I believe this ‘addiction’ makes me better as a software engineer. I think most people would agree that tested code is likely to be better code, and strong unit test proponents would probably argue that the habit makes you write better code whether or not you unit test a given class at a given time. For example, when I create a dependency-injected class, the first code I tend to write, automatically and out of habit, is an “if” statement in the constructor that checks the injected references for null and throws an exception if they are. I write this because the first unit test that I write is one to check how the class behaves when injected with null.
And, to me, that’s really the most core benefit of unit testing. Sure, it makes refactoring easier, it enumerates your requirements better than a requirements analysis document could, it inspires confidence in the class behavior, and all of the other classic properties of unit testing as a process stalwart. But I think that, at the core, it changes the way you think about classes and how you write code. You write code knowing that it should behave in a certain way with certain inputs and that it should provide certain outputs. You think in terms of what properties your classes have and what they should initialize to in different instantiation scenarios. You think of your classes as units, and isn’t that the ultimate goal — neat, decoupled code?
One common theme that I see in code and I’ve come to think of as highly indicative of a non-unit testing mentality is a collection of classes that distribute their functionality in ad-hoc fashion. That is, some new requirement comes in and somebody writes a class to fulfill it — call it “Foo.” Then a few more requirement riders trickle in as follow ups, and since “Foo” is the thing that satisfies the original, it should also satisfy the new ones. It’s perfectly fine to call it “Foo” because it represents a series of user requests and not a conceptual object. Some time later, more requirements come in, and suddenly the developer needs two different Foos to handle two different scenarios. Since Foo is getting kind of large, and large classes are bad, the solution is to create a “FooManager” that knows all about the internals of “Foo” and to spread functionality across both. If FooManager needs internal Foo field “_bar”, “_bar” is made into a property “Bar” (C#) or an accessor “GetBar()” (Java/C++), and the logic proceeds. Foo does some things to its former private member “Bar” and then “FooManager” also does some things to Foo’s Bar, and before you know it, you have a Gordian knot of functional and temporal coupling.
I don’t think this kind of code would ever exist in a world populated only by unit testing addicts. Unit testing addiction forces one to consider upfront a class’s reason for existing and carefully deliberate its public interface. The unit testing addict would not find himself in this precarious situation. His addiction would save him from this “rock bottom” of software development.