Xcode: Unit Testing

Xcode 2.1 introduced integrated unit testing to the Xcode IDE. Xcode includes two unit testing frameworks, target templates for setting up test bundle targets, and infrastructure for running unit tests every time you build your project and reporting their results in the Build Results window just like compilers and linkers do.

With Xcode unit testing, you group your test cases into test bundles which are built by separate targets. This means that you don’t need to build two versions of your software — one with tests and one without tests — and can run your tests against the actual software you want to deliver.

Xcode 2.1 and later include OCUnit for unit testing of Objective-C Cocoa software, and it includes a new framework called CPlusTest for unit testing of C++ software. Using either you should be able to test C code with relative ease. Corresponding target templates are included for creating unit test bundles appropriate for Cocoa and Carbon applications and frameworks, and file templates are included for creating OCUnit and CPlusTest test case classes.

The test bundle target templates have a shell script build phase at the very end that invokes /Developer/Tools/RunUnitTests. RunUnitTests looks at the build settings it’s passed via its environment and determines from that information how to run the tests in your test bundle.

If you’re testing a framework, RunUnitTests will run the appropriate test rig and tell it to load and run the tests in your bundle. Since your test bundle should link against your framework, your framework will be loaded when the test rig loads your bundle.

If you’re testing an application, you need to specify the application as the Test Host and Bundle Loader for your test bundle in its configuration’s build settings. The Bundle Loader setting tells the linker to link your bundle against the application that’s loading it as if the application were a framework, allowing you to refer to classes and other symbols within the application from your bundle without actually including them in the bundle. The Test Host setting tells RunUnitTests to launch the specified application and inject your test bundle into it in order to run its tests.

There’s even support in RunUnitTests for invoking a test rig of your own, rather than the test rig for one of the supplied frameworks. You just need to specify the Test Rig build setting for your test bundle; this should be the path to a tool to run. It will be passed the path to your test bundle as its first argument, and if it needs to generate failure information it can just generate it in a gcc/compiler-like format on stderr:

FailingTest.c: 10: error: (1 == 0) failed

This will cause it to show up in the Build Results window as an error, just like a compiler or linker error. You can see the RunUnitTests manpage for more information on the environment in which your test rig will be run.

There’s a great Unit Testing Guide on the Apple Developer Connection web site that has lots of information on getting started with Xcode unit testing. Check it out!

WWDC 2005 Wrap-Up

WWDC 2005 is over, and *damn* was it a great week! Apple made some incredible announcements and shipped some incredible software, I got to see lots of old friends and make a lot of new ones, and I got to talk to lots of developers about things that I’m passionate about: Core Data, unit testing, setting up and streamlining your build process, and creating insanely great software to make users’ lives better.

It was a wonderful, wonderful time. Thanks to everyone!

Unit testing and Core Data

Mike Zornek asks about unit testing and Core Data. I’ve been meaning to write about this, so this is the perfect opportunity to do so.

Writing unit tests against your model and code that uses Core Data is easy. For example, it’s trivial to load your compiled model in a unit test:

NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];

Not only that, but you can introspect it:

NSArray *entities = [model entities];

And you can do this all the way down to the property level. This means that it’s possible to assert that your entire model is set up the way you expect it to be. For example, you can make sure that your Employee entity has a mandatory salary attribute with a minimum value of 1 and a type of NSDecimalAttributeType, and descends from a Person entity that has a mandatory name attribute with a minimum length of 1 and a default value of “name.”

But how do you test your use of Core Data? You just use Core Data in your tests as you would in your project. For example, to instantiate a complete Core Data “stack” (as it’s sometimes referred to):

NSManagedObjectModel *model;
NSPersistentStoreCoordinator *coordinator;
NSManagedObjectContext *context;

model = [[NSManagedObjectModel alloc] initWithContentsOfURL:urlToModel];
coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:coordinator];

To instantiate managed objects associated with that context from entities in your model:

NSManagedObject *employee;

employee = [NSEntityDescription insertNewObjectForEntityForName:@”Employee”
inManagedObjectContext:context];

This gives you an autoreleased Employee (assuming your context’s coordinator’s model has an Employee entity, of course). It’s that easy.

You can then do things like check that this object was created with the correct defaults (e.g. the ones specified in your model), that it posts KVO notifications properly for properties where you care about such things, and so on. You can even add a persistent store to the coordinator and test that saving and loading work (and don’t work when they’re supposed to fail, of course) just by using normal Core Data and Foundation APIs.

You can multiply the full power of data modeling with the full power of unit testing and test driven development. It kicks ass.