Alastair’s Place

Software development, Cocoa, Objective-C, life. Stuff like that.

Running Xcode Unit Tests From the Command Line

For some time now, Xcode has included support for unit testing via an integrated copy of Sen:te’s OCUnit. On the face of it, this is a great feature as it lets you easily write unit tests and straightforwardly integrate them into the Xcode build process.

Unfortunately, there is very little documentation in Xcode itself relating to the unit testing feature. In fact, all I can find is the pointlessly short Xcode Unit Testing Guide.

It’d be really handy if the various STAssert macros were documented in the API documentation; constantly having to open up the SenTestCase_Macros.h header file is a bit of a pain, frankly. Since it’s now possible to put third-party docs into the Xcode Documentation window, perhaps someone will take the time to document the various macros?

But it isn’t nearly as much of a pain as the fact that there is no explanation anywhere of how to run your unit tests from the command line, or indeed how to debug a failing unit test.

Running otest from the command line

Trying to run a unit test from the command line can be a real headache. Especially if the machine you’re testing on happens to be 64-bit capable, because when you try to run otest from the command line, OS X will choose the 64-bit variant, which probably won’t match the test bundle. Either way, if you’ve tried this and you haven’t seen error messages like “no suitable image found” or “GC capability mismatch”, frankly you’ve been very lucky.

The best way to show how to avoid these problems is probably to give an example, so here goes:

<blockquote class=”code””> $ find . -name '*.octest'
./build/Debug/MyTests.octest
$ export OBJC_DISABLE_GC=YES
$ arch -i386 /Developer/Tools/otest ./build/Debug/MyTests.octest </blockquote>

The first line finds the test bundle. You don’t really need to do this every time (or at all, if you know where it is already).

The second line disables GC; this is optional, but if the code you are testing doesn’t support GC and the version of otest on your system was build with GC enabled, you need to disable it as shown here or you’ll get a “GC capability mismatch” error. If you’re seeing “GC capability mismatch” and your code is build to require GC, you probably need to update your version of Xcode (3.1.1 comes with an otest that does work with GC) or if that isn’t an option you could build a suitable otest binary yourself.

The third line is where things really get interesting. You can’t rely on just running otest, since the system you’re running on might choose the wrong architecture for your test bundle. Fortunately Apple has provided the arch command (see man 1 arch), which lets you ask for a particular architecture when running something from the command line. In the example, I asked for 32-bit x86, but I might just as well have asked for any number of other things.

If you’re unlucky enough to be trying to debug a framework unit test, you’ll also be having problems at this point with the system not finding the framework. To fix this, you can add something like

$ export DYLD_FRAMEWORK_PATH=`pwd`/build/Debug

to the above set of commands.

Running your unit tests in GDB

OK, so we know now how to run the unit tests from the command line. How do we get them to run in GDB? Simple. Something like

$ gdb -arch i386 /Developer/Tools/otest GNU gdb 6.3.50-20050815 (Apple version gdb-962) (Sat Jul 26 08:14:40 UTC 2008)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-apple-darwin"...Reading symbols for shared libraries ...... done

(gdb)

Notice the -arch i386 at the top… that sets the architecture, which you need to do at the GDB command. Then we proceed as before, but this time using GDB commands rather than shell script

(gdb) set environment DYLD_FRAMEWORK_PATH=/Users/alastair/Source/UnitTestTest/build/Debug
(gdb) set environment OBJC_DISABLE_GC=YES
(gdb) set args ./build/Debug/MyTests.octest
(gdb) run

Again, the DYLD_FRAMEWORK_PATH and OBJC_DISABLE_GC steps are both optional and whether you need them or not depends on your particular circumstances.