OCUnit: Integrated Unit Testing In Xcode

By
On May 4, 2009
Guest author Kailoa Kadano (Profile) is a full-time iPhone developer and entrepreneur. Kailoa’s experience ranges from embedded systems to python web development. He currently resides near Santa Cruz, California.

When the original iPhone SDK was released it lacked unit testing capabilities. Third-party solutions were adapted or created to fill the gap. However, in the interim, with the 2.2 version of the SDK, Apple has quietly brought back the OCUnit unit test solution.

One of the biggest advantages of Apple’s OCUnit implementation is the tight integration into Xcode. This means you get IDE errors instead of having to parse the console output.

Using OCUnit

Starting with an existing Xcode iPhone project:

  1. Add a new Unit Test bundle target using the Project > New Target… menu items. Select Unit Test Bundle under the Cocoa grouping in the Mac OS-X section.

    New Unit Test Bundle

  2. Modify the “Other Linker Flags” setting:

    We need to change the Cocoa value to Foundation. This will allow us to compile for the iPhone Simlulator

    Other Linker Flags

    Lastly, search for any Cocoa.h references and delete them:

    Cocoa.h Reference In Build Settings

  3. Add a test file with a TestCase subclass in it:

    Using the Mac OS X > Cocoa > Unit Test Case Class template, create and add a new test case file.

    Test Case files must end in TestCase (e.g., MathTestCase or BackEndTestCase). Similarly, all test methods should start with test. The underlying test mechanism uses some very nifty runtime magic to determine which classes and methods to run based on their names.

    Add Unit Test Case

    Make sure you are only adding to the Unit Test Bundle and not to the main project:

  4. Add a test:

    Here’s a easy one you can use to test the test framework itself.

    - (void)testTestFramework
    {
        NSString *string1 = @"test";
        NSString *string2 = @"test";
        STAssertEquals(string1,
                       string2,
                       @"FAILURE");
        NSUInteger uint_1 = 4;
        NSUInteger uint_2 = 4;
        STAssertEquals(uint_1,
                       uint_2,
                       @"FAILURE");
    }
    
  5. Build the test bundle; make sure tests pass.

  6. Build the test bundle; make sure tests fail:

    Again, just to exercise your test framework, temporarily change one of the strings to force a test failure.

    Force A Failure

  7. Add the unit test bundle as a dependency:

    Now, we want the unit tests to run every time we build. We can do this by setting a dependency on the Unit Test Bundle. Switch back to the main project target (away from the unit test bundle) and Edit the Active Target. Click the + button under to add and new Direct Dependancy.

    Add Dependancy

Now you have an automated testing framework in place. Every time you build, you will be immediately notified of any test failures.

For a complete, working example download this sample project.

An Eesthetic Note About Header Files

Test Case files header files are nearly always empty, so I usually get rid of them, creating single file test cases. The resulting .m looks like this:

//only run on the simulator
#include "TargetConditionals.h"
#if !TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
#import <SenTestingKit/SenTestingKit.h>
@interface SampleTestCase : SenTestCase
{
}
@end
@implementation SampleTestCase
- (void) setUp
{
    // Optional
}
- (void) tearDown
{
    // Optional
}
- (void)testTestFramework
{
    NSString *string1 = @"test";
    NSString *string2 = @"test";
    STAssertEqualObjects(string1,
                         string2,
                         @"FAILURE");

    NSUInteger uint_1 = 4;
    NSUInteger uint_2 = 4;
    STAssertEquals(uint_1,
                   uint_2,
                   @"FAILURE");
}
@end
#endif

Alternatives

Other iPhone based testing frameworks and utilities:

Google’s implementation was one of the first publicly available for the iPhone. They’ve apparently built a lot of infrastructure around their solution to fit within existing test systems. GHUnit attempts to integrate the unit test in the Simulator itself and the properly named OCMock does mock objects.

This article originally appeared in Kailoa’s blog. Have an article, or an idea for an article, that might interest our readers? Contact us!

0 responses to “OCUnit: Integrated Unit Testing In Xcode”

  1. Hi Kailoa,

    great tip! I’ve been using Google’s toolkit and OCMock and they are doing a nice jog. I’ll sure try this OCUnit as soon as possible.

    I’d like to suggest a framework for iPhone Interface Test that I’ve been developing for a few months: Bromine

    I would be nice if you could take a look and make a review.
    Thanks

  2. Good article. One gotcha I found is that you have to build with Active SDK 2.2 or later. This is a little bit of a bummer if you want to build your app for users on 2.0 or 2.1.

  3. Robert says:

    Very nice quick tutorial. And the example works as you’ve given.

    However as soon as I import one of my application classes in my *TestCase.m, and do nothing more than try to initialize an instance, the testing gives an error message (in the Build Results panel) like this:

    (x)Linking /Workspace/MyProject/build/Debug-iphonesimulator/TestCases.octest/TestCases (1 error)
    (x)”.objc_class_name_MGPolylineRaw”, referenced from:
    literal-pointer@__OBJC@__cls_refs@MGPolylineRaw in FooTestCase.o
    symbol(s) not found
    collect2: Id returned 1 exit status

    (If anyone can help me learn what is going on I’d be most appreciative. I’ve been writing obj-c code for just over a month now and this is one of those things which is beyond my understanging at this stage…)

  4. Kailoa says:

    @ Robert,

    You need to add your application classes to the Unit Test Target.

    Right click on the Groups & Files header of the left pane of the project window.
    You should see a bunch of check boxes. Add any .m files you import into the test cases. Should work.

  5. Kailoa says:

    @Felipe,

    Thanks for the pointer, I’ll check it out.

    @Don,
    You are right, 2.2 is required. Sorry I didn’t make that more clear. If you need to do testing against 2.1 or 2.2, I believe GTM supports it.

  6. Meinhard says:

    Very helpful tutorial to start unit testing in Objective-C.

    But I am feeling more comfortable when I first write a test which fails and develop further to get it passed. 😉

  7. peanut says:

    Nice article. I’ve heard about SenTestingKit in PeepCode screencast named “Objective-C for Rubyists“.

    What about using this testing kit in testing OpenGL applications? Does it any troubles? Or better way to test OpenGL apps is other testing kit?

  8. Mark Schapira says:

    The article is helpful.
    However, if a have a unit test that fails, I cannot use the debugger to
    set breakpoints in my code to see what when wrong. I seems that the
    error messages appear and the debugger never stops at the breakpoint
    that I set.

  9. Joel says:

    Thanks–this was a great tip.
    Two bits of clarification, in case anyone else confuses as easily as I do:
    1) It appears that at least as of XCode 3.1.3 there is a “Unit Test Bundle” target-template item under Cocoa Touch, in addition to that under Mac OS X. Selecting this template appears to pre-fab all the linker flags and user settings editing you described–saves a bit of work.
    2) Re: comment of 5/10/09, about adding project files to the Unit Tests target–newbies like me might need a bit more detail:
    – Set Active Target to be your Unit Tests (Use menu Project->Set Active Target)
    – In XCode window, right-click “Groups and Files” header, and select “Target Membership”. This will reveal checkboxes to left of project files. Selecting checkboxes adds corresponding files to Unit Test target.

  10. Willsiqueira says:

    Hello everyone!

    I trying to create Logical Unit Tests on iPhone, XCode 3.2 and SL 10.6.1. I´ve added my own classes to the Unit Test Bundle and import these classes to my test class but, when I try to build a simple test that only alloc/init one of my classes, I´m receiving a lot CoreGraphics and UIKit errors (CGFloat UIVIEW) . I really dont know what to do to solve this. I´ll really appreciate any help!

  11. Dave says:

    Would it not be better to make the app a direct dependency of the test bundle and build with the test target.

    What you have done seems the wrong way around to me – resulting in Robert’s issue.

  12. Dave says:

    @Mark Shapira

    I think this is because the product executable being run by GDB does not load your test bundle. I think you need to use the DevToolsBundleInjection.framework for this. IIRC there was an article on ADC describing this. “Automated testing with XCode 3 and Objective-C” I think.