Opiates and Testing

After more than a year off from blogging I managed to wander into my blog drafts folder and found a mostly finished blog post from past ages. Perhaps an old post brought to life will kickstart new blog posts so without further ado, here is a tale of painkillers, programming, and pontifications.


My discovery of unit testing and test driven development was a happy accident due to desperation and vicodin.

In my second year of college I was took a computer science course on algorithms and data structures. All was going well until I was in a bad rock climbing accident, fell a good 30 feet or so, and broke my right foot in about 10 places. (Approximately. The doctors lost count of all the fractures). I tried soldiering on in that algorithms class, but I discovered after the fact that vicodin and programming DO NOT MIX. Things got ugly when I tried writing a binary search tree. I would happily bash out code and nested if statements, try to run it, and watch my program spin off in an endless loop. It turns out that in my attempt to write a binary search tree, I had invented a whole new data structure - the binary wreath. It was bad.

I wound up taking an incomplete in that course so that I could focus on healing, and the second time around I decided that I needed a better approach to writing code. The development methodology that my university taught was “write code until it works,” but my intersection with programming and strong pain killers indicated that this was the road to madness. I needed some way to be able to incrementally write code and ensure that it worked. Unit testing was a phrase that I heard before, and I decided that it was worth a shot. I fired up Eclipse, read up on JUnit, and started a fresh implementation of a binary search tree.

While my previous development approach was to assemble code in the same manner that college students balance dirty dishes in the kitchen sink, I started on this new attempt by writing small clumps of code and tests. I would write a test for the most basic thing - such as an empty list has zero elements. I would then write a size() function, run the tests and fix bugs till the tests passed, and onward I would go. Bit by bit I assembled more and more complex data structures that eventually resembled trees more closely than bushes.

Initially I found this approach rather time consuming. By writing all these tests I had doubled the amount of code that had to be written, at the least. I found it hard to write code that I could test, and I kept finding that I had to write basic test cases that seemed rather silly. It didn’t help that I was trying to finish an incomplete, and as college students do I had procrastinated as much as possible so I didn’t have a lot of time to waste.

But as I discovered, the initial cost of writing those tests became more and more valuable. The tests that I thought were silly tests kept on catching subtle bugs that would have cost me hours of debugging later. I spent so much less time backtracking, fixing regressions, and tearing my hair out. Because I had this tight cycle of writing tests and writing code it was easy to see how complete the code was, what edge cases I still needed to handle, and the like.

A while later I wound up transferring to a new university, and I found that I had to either repeat an algorithms course or test out of it. The latter was much more desirable, so I had to brush up on algorithms to make sure that testing out was a breeze. I started implementing linked lists and binary search trees in C and C++, and I realized that I could actually work faster if I implemented tests all along the way. I had these systematic guarantees that certain things worked, and I could make changes with the confidence that if I made a regression, I would know about it. I wound up reusing the code I wrote for other classes and I kept expanding my own standard library, complete with unit tests.

Unit testing has become critical in my development methodology. It serves to validate and document code, and it forces you to think about your code in a more discrete manner. You would think that with these sort of benefits, both academia and the tech world would be all over testing and this blog post would be a no brainer, right?

In practice, effective testing is frequently brushed off as an unnecessary burden. All too often changes are made by writing code until the sheer accretion of code makes things work, and then it’s off to GitHub to file a pull request that vaguely resembles the Tower of Babel. And to be completely honest, it’s easy to just merge that pull request rather than coercing the contributor to write the tests or go through the arduous process of writing all the tests yourself.

But every time you skip the testing aspect of development, you’re screwing someone over. Sometimes it’s yourself, sometimes it’s your colleagues, and sometimes it’s some poor developer who got the hand grenade of untested code tossed in their lap. One of the projects that I’ve worked on had a release that went horribly sideways and had to be yanked, and it was partially on me. Someone before me wrote a huge swath of code with no real tests, I assumed that the tests were good and didn’t add coverage for the code that I changed, and the shock wave from the resulting explosion circled the earth 7 times before dissipating.

Testing your code from the beginning is more than just making sure things work; it makes you think about your code design. You have to clearly understand what values a method might receive, and understand what the desired output is. You have to write modular code, or the tests will quickly grow too complex to write. You have to avoid side effects, because testing side effects can become a serious pain in the ass. Tests force you to be accountable to yourself now, rather than in the future when you’re chasing down bugs or grappling with your interfaces.

Looking back, I can’t think of any particular time where I actually regret writing tests. There are many times where I moaned about the misery of tests, but when all was said and done I was always glad that I had them. I’m still paying back the debt of incomplete test coverage on my own projects, but every time the test coverage percentage goes up on a project, it feels like the quality of the entire project goes up even more.

So seriously. Just write the damn tests.

Addendum: In no way do I advocate the use of vicodin for anything but medical cases that are a 10 or above on the Hyperbole and a Half pain scale. Stay in school, kids.

Footnotes

libf

Short for libfinch. I was terribly creative.

I even put it on GitHub.