Bubble Foundry

iOS Code Coverage Revisited

by Peter.

My earlier post on iOS code coverage reports was, if not wrong, at least sub-optimal. You still need to set Generate Test Coverage Files and Instrument Program Flow to Yes for the Debug build configuration.

Instead of manually flushing the coverage data upon finishing each test, it’s better to flush it have the entire testing. Likewise you don’t need to add any of your main source code files to the Compile Sources build phase of your test target. (All this is thanks to this Stack Overflow answer.)

You do this by adding a class that extends XCTestObserver to the test target, e.g.:

@implementation GcovTestObserver
- (void) stopObserving
    [super stopObserving];
    UIApplication* application = [UIApplication sharedApplication];
    [application.delegate applicationWillTerminate:application];

Then, in AppDelegate.m add the following to - (void)applicationWillTerminate:(UIApplication *)aApplication:

#ifdef DEBUG
  extern void __gcov_flush(void);

And finally, add an initialize method to AppDelegate in order to add our GcovTestObserver class as a test observer:

#ifdef DEBUG
+ (void)initialize {
    [[NSUserDefaults standardUserDefaults] setValue:@"XCTestLog,GcovTestObserver"

Now coverage reports will be created for all the source code files in your project.

However, there’s one final issue: if you’re using the The Xcode CI service in OS X Server, the .gcda files won’t be created. The Xcode CI service creates the directory where the coverage files are stored with the _teamsserver user and 755 permissions. Since tests are run, and thus coverage files created, by the _xcstest user, coverage files cannot be written to the directory. Because _xcstest is a member of the _teamsserver group, just making the directory group-writable fixes the problem.

To do this, add a Run Script build phase to the main target. The script is very simple:

chmod 775 ${OBJECT_FILE_DIR_normal}/i386

Update, 2013-12-30: A reader, Huangshuai, discovered that coverage reports will not include view or view controller classes that initialized in XIBs. The only solution seems to initialize the class in question using the traditional alloc init dance.