09 September 2011 by Published in: Code, iphone 5 comments

It seems like every time a new feature gets checked in, somebody fails to add it to the test target, and I get a linker error like this one:

Apple Mach-O Linker (ld) Error: _OBJC_CLASS_$_CLASSNAME referenced from…

The solution to this is not to just keep adding files to the test target.  If you compile a source file both into your test target and your app target, you are compiling the same code twice.  While on the surface this doesn’t sound so bad, in fact it can be:

Class MYCLASSNAME is implemented in both EpicPath1 EpicPath2. One of the two will be used. Which one is undefined.

This doesn’t sound so bad either, if you take a conservative view of “One of the two will be used”.  After all, they’re both the same class.  Apple, however, has quite a more whimsical outlook on things.  Which class definition is used can change many times within a single compilation unit.  So if you do something like this:


@implementation MYCLASSNAME

static BOOL mysharedVariable = NO;

+ (void) setMySharedVariable:(BOOL) value {mySharedVariable = value; }

+ (void) test
{
   [self setMySharedVariable:YES];
   NSLog(@"%d",mySharedVariable); //prints 0 or 1 with ~50% probability
}

It really gets surreal when you set the variable in one function, and you read it two functions deeper in the stack.  Set your debugging context to the inner function, it returns 0.  Set your debugging context to the outer function, it returns 1.  Then you wonder if you will look back on the debugging session as the time when the voices started.

So what you should actually do is this:

  1. Get everything that’s not a unit test out of the unit test target.  No more application code in there.
  2. Set BUNDLE_LOADER=$(BUILT_PRODUCTS_DIR)/myapp.app/myapp
  3. Set TEST_HOST=$(BUNDLE_LOADER)
  4. ensure “Symbols hidden by default” is NO in your app target (not your test target).  This lets the linker reach into the application bundle and use its symbols in your test target.  Depending on how serious you are about preventing reverse-engineering, you might want to turn this to YES for release builds.
The other advantage is that your unit test target is now compiling like 2 files, which is blazing fast.  Now the time between pressing Command-U and getting a completed unit test log is something like 10 seconds for an enormous project.  This is fast enough that people will actually do it.

 


Want me to build your app / consult for your company / speak at your event? Good news! I'm an iOS developer for hire.

Like this post? Contribute to the coffee fund so I can write more like it.

Comments

  1. Sat 04th Feb 2012 at 11:03 pm

    Thanks very much! This resolved the aforementioned error that I ran into when adding my first test. The test ran fine when I didn’t reference any of my code but failed as soon as I did (so I think my bundle/framework path was entirely broken, as opposed to compiling twice).

    Probably obvious, but I wanted to mention that you need to build the simulator version prior to running the unit tests.

    Thanks again!
    Owen

  2. Drew Crawford
    Sat 04th Feb 2012 at 11:04 pm

    Probably obvious, but I wanted to mention that you need to build the simulator version prior to running the unit tests.

    You may want to add the app target as a target dependency of the test target. This will enforce that the app target builds first.

  3. Vikram VI
    Wed 25th Apr 2012 at 8:04 am

    Hi Drew,
    thanks for the useful info. After I changed my setting per your blog , I’m getting new error as
    ld: -bundle_loader can only be used with -bundle

    1. I’m using XCode 4 and GHUnit.
    2. From target project , I’ve created an object of class in main app.
    3. From this object I’m trying to call the method in main app.

    Can you please help me out with this ?
    Also can you please share any sample unit test cases written which I can refer to will be of great help.

    Thanks and Regards,
    Vikram

  4. Drew Crawford
    Wed 25th Apr 2012 at 9:55 am

    I’m afraid I don’t know anything about GHUnit, I always use SenTesting which ships with XCode.

Add comment

Powered by WordPress