OCUnit: Integrated Unit Testing In Xcode
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:
-
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.
-
Modify the “Other Linker Flags” setting:
We need to change the
Cocoa
value toFoundation
. This will allow us to compile for the iPhone SimlulatorLastly, search for any Cocoa.h references and delete them:
-
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 withtest
. The underlying test mechanism uses some very nifty runtime magic to determine which classes and methods to run based on their names.Make sure you are only adding to the Unit Test Bundle and not to the main project:
-
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"); }
-
Build the test bundle; make sure tests pass.
-
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.
-
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
.
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.
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
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.
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…)
@ 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.
@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.
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. 😉
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?
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.
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.
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!
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.
@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.
@Dave: ADC article here https://developer.apple.com/mac/articles/tools/unittestingwithxcode3.html.