My first unit tests

2007-08-24 12:39:44 UTC

Last night, I added unit tests to Adium. Despite the fact that I have espoused adding tests for some time (usually when stuff broke that would have been caught by an automated test), I actually had never created tests before.

I love it.

Here’s why I added tests, and my quick overview of how to do it.

Why

I started out looking at fixing #7713. The problem was that our display of intervals was including units of which there were zero: the example from the ticket is “1 Week 4 Days 0 Hours 43 Minutes”. (We don’t really capitalize those letters, in case you’re wondering.)

Upon investigating, I found that the method that creates these strings was complex and thinly commented, and it didn’t work in the obvious way (building an array and then joining its elements). The upshot of that was that I could guess that that the method was probably deeply broken, but in such ways that made it not obvious exactly how it was broken. This left me uncertain of how to proceed, since I had the sneaking feeling that any change had a chance of breaking something else, even if it fixed #7713.

What I needed was a complete set of diagnostics that would attempt to extract every combination of time units and make sure that all of the results were in line with my expectations.

In other words—a test suite!

The major mental block that had kept me from getting into unit testing had always been how tautological it seemed: having just written some code that does X, you then write code that makes sure X got done. This seemed redundant, mostly because I had been envisioning assigning to a variable, then checking the value in the variable to make sure it really got assigned. (Obviously, that’s wrong. I never seriously thought that people were really doing that, but couldn’t think of what else you would test.)

Now, I had a case that clearly and finally proved that real unit testing isn’t anywhere near tautological: I had code that’s broken, that I could break in some other way by attempting to fix it. Thorough testing would help me find all the ways in which it’s broken, so that I could fix it without breaking something else.

This is exactly what unit testing is for.

How

Here’s a brief summary of what you need to do to set up your project the unit tests (assuming that you’re using Xcode, version 2.1 or later):

  1. Create one or more subclasses of SenTestCase.

    Here’s what one of our tests looks like:

    - (void)testDateFormatterStringRepWithInterval_minutes {
    	//Note that we want a date in the past, because we want to describe the time interval since that date.
    	NSDate *date = [[NSCalendarDate calendarDate]
    		dateByAddingYears:-0
    		           months:-0
    		             days:-0
    		            hours:-0
    		          minutes:-10
    		          seconds:-0];
    	STAssertEqualObjects([NSDateFormatter stringForTimeIntervalSinceDate:date], @"10 minutes", @"Unexpected string for time interval");
    }

    Every individual test should be in its own method in your class, and that method’s selector must begin with “test”. Go wild—you don’t need to list them anywhere, because the test runner detects them all automatically (hence the selector-begins-with-“test” requirement). So don’t worry about having lots and lots of methods, as long as they’re appropriately categorized in different SenTestCase subclasses (for the sake of small, easily-navigable files).

    And yes, you do need to subclass SenTestCase, as it provides methods that the STAssert… macros need.

  2. Add a Cocoa/Unit Test Bundle target to your Xcode project.

  3. Add your SenTestCase subclasses to the bundle target.

    You probably should not put the test case class in your main application or framework targets. I know I can’t think of a reason why you would.

  4. Configure the Info.plist of the bundle in the usual way (Get Info on the target, then set the bundle ID and signature on the Properties tab).

Henceforth, whenever you want to run your tests, build the bundle target. You don’t need to run it, because the last phase of the target is a shell script that runs the tests. This is so that you can make the test bundle target a dependency of your main application/framework target, in order to have it run whenever you try to build the app/framework.


(Also, this is the 500th post in my blog’s WP database. Woo-hoo!)

One Response to “My first unit tests”

  1. Drew Thaler Says:

    Yay for unit tests!

    It’s funny how long it sometimes takes to get them added to an existing project. The (internal) project I’m working on now was under development for a good year and a half before I finally had time to really sit down and write some proper unit tests. (My excuse: it was for PS3 and the platform was imminently about-to-launch for almost a year of that time.) Now that I’ve got them I’m infinitely less afraid of accidentally breaking something with a change — if it passes the automated testing, then I know things are fine.

    Xcode makes it very easy for ObjC with OCUnit. If you need to do the same for C++, we’re using UnitTest++ at work and have been very happy with it. Despite the minimal docs it’s got pretty much everything you need (including fixtures, which are a must) and it’s super lightweight and simple to work with. I do believe it’s got the Wolf Rentzsch stamp of approval too.

Leave a Reply

Do not delete the second sentence.