May 8, 2013

How Unit Testing Saves Lives, or Your Sanity

For the longest time, I didn't really understand what it meant to unit test code. I remember thinking something along the lines of "wait, so I write a function that adds one to a number. then I write a test to prove that it added one to a number?" It just seemed to me, as I'm sure it does to many people, like extra work, and unit testing wasn't worth the overhead involved it testing all of my code. I mean, I tested it all when I was developing it, right?

Those were the good old days of back end development, when all you needed to do to test your app was hit your web interface, validate input for malicious data, and display error messages to users when they missed a spot filling in a form. Maybe you used an IDE for remote breakpoint debugging, but old school error logging usually did the trick just fine. Load page, fill form, validate error conditions, rinse, repeat.

In a mobile development world, the days of "error log" style debugging are long over. If you're at a small shop, you may have the luxury of having control of the whole stack and codebase. Even then, the typical mobile stack is comprised of a C/Obj-C/Java layer, proxying software (Charles Proxy etc) to deal with firewalls, back end code, and a data storage layer. When working on larger teams of both client, server, and third party server developers, it's incredibly common to get blocked by someone else's bug, making it impossible to even hit your code until the problem is fixed upstream.

On a recent project of mine, I found myself blocked like this on a very regular basis, so much so that I was given time to learn how to use unit testing to save countless time and energy while developing features.

The Problem

I was tasked with building a sizable API in a short timeframe -- sound familiar? -- and my only mechanism for testing my code was a mobile client. The client was also in development, and was understandably buggy. It was inefficient to try and test my code from the client, because on a daily basis, something went wrong, and I was SOL for hours at a time. This approach also forced me through an entire stack worth of code when it was preferable to test small units of code independently.

The Solution

Unit Testing to the rescue! By setting up PHPUnit on a CentOs server -- which incidentally required a slight hacking of its code to run with PHP 5.2.x -- I was able to test all of my features without needing to use the client for weeks at a time. For any given new feature, immediately after writing an API method I would write unit tests to serve the following purposes

  1. Validating that the method behaved as expected with correct input
  2. Validating that the method behaved as expected with incorrect input
  3. Verifying that the tests have achieved 100% code coverage of the new method

One important distinction that was lost on me was the difference between assertion testing and code coverage testing. Assertion testing involves validating that your API methods return the proper output based on correct and incorrect input. Code coverage_ testing involves writing enough tests so that every line of your API code is hit by the tests.

What have unit tests done for me lately?

By running the unit tests on a regular basis when code is updated, you can verify that no one has made changes that break the expected functionality of your API. Similarly, unit tests of known, valid input to third party APIs can alert you when something has changed underneath your working code to break it. If new changes do break the API, then you can update the tests accordingly, and move on knowing that your production code is working as expected. Additionally, unit tests also serve as bite-size examples of how to use an API.

I still think it's too much work. Convince me otherwise

For a medium to large codebase that's not at all unit tested, you're right. I ended up starting by using bug fixing time as an opportunity to refactor code and write tests against my bug fixes. This breaks up the work, and over time helps to stabilize an unruly codebase. The greater your code coverage, the quicker it is to locate and extinguish fires when your team is small.

Some tips for the up and coming unit tester

These tips might be of use to you

  • Keep your methods small and remember to stay DRY.
  • Think about what your method signatures should be in order for them to be easily testable.
  • Check out Jenkins if you haven't already.