Acorn Heroes

Unit Testing and linking Static Libraries with XCode

by George on Jun.21, 2009, under Coding

For some time I’ve been wanting to set up unit testing as a part of my iPhone development process. Being mainly a Visual Studio developer, I’ve yet to learn all the ins and outs of XCode and GCC projects, so it seemed like a good chance to learn more.  There are a number of tools available.  I was keen to try out UnitTest++ by Noel Llopis and Charles Nicholson, as it is a) light weight, and b) designed for use with C++, which a large portion of our code will be. Noel has several articles about unit testing that I’d recommend reading.

So here’s the plan.  I want to set up libraries of useful code that I can re-use across projects.  For the iPhone this currently means statically linked libraries.  Next, I want to create unit tests for the libraries and have the tests run as a part of my build process.  If you just want to get your hands on a working sample, grab it here.

OK, let’s get started.  I’ve created a new project in XCode.  Any will do, but I like to see something visual, so I created an OpenGL application called CoolGame.  By default, XCode adds new files into one monolithic folder, which is a pain.

Instead, open the project folder in the Finder, and add a folder called UnitTest++ under the main project folder, for example,  {YourProjectPath}/CoolGame/UnitTest++.  Download the latest version of UnitTest++. Open up the downloaded folder, we want to copy all the files from the src folder into our UnitTest++ folder.  You can leave out the Win32 and tests folders if you like, we won’t use them.

Going back to XCode, control-click (or right-click) on ‘Targets’ and add a new iPhone static library target, calling it UnitTest++.  From the finder, drag and drop the UnitTest++ folder into XCode, just below the project name in ‘Groups & Files’.  In the dialog that pops up, uncheck ’Copy items…’, select ‘Recursively create groups…’ and ensure we’re adding them only to the UnitTest++ project.  Control click on the UnitTest++ target, and try building it.  If all is going well, it should build happily.  Now we need a test case to prove it’s all working nicely.

Lets add two new targets to the project.  One is a static library (‘FrameRateLibrary’ in my case), and the other is an application (‘FrameRateLibraryTest’).  FrameRateLibrary represents our library code, and FrameRateLibraryTest will run unit tests on that library.  FrameRateLibrary will also be used by our main application, CoolGame.  In a larger project, you might have multiple versions of these, one for your renderer, one for a sound, one for UI and so on.

Control click on ‘Groups & Files’ to add a new group called FrameRateLibraryTest.  Add a second one called FrameRateLibrary.  This will help us keep the code nicely organised. It may seem a bit strange, but we’ll get FrameRateLibraryTest working before FrameRateLibrary.  This is a fairly common approach with Test Driven Development – prove the test works before you write the code to pass it.

Control click on the FrameRateLibraryTest Group to add a new C++ file.  I’ve called it FrameRateLibraryTestMain.cpp.  In the dialog that pops ups, uncheck the ‘create a .h file’ option (we won’t need one), set the location to be {YourProjectPath}/CoolGame/FrameRateLibraryTest, and ensure that it’s being added to the FrameRateLibraryTest target only.  You’ll be asked if you want to create a new folder, say yes.

I added the basic code below, then tried to build the test application and it failed.  Apparently the build is trying to include some Object-C code from somewhere, probably a precompiled header file.  If anyone can fill me in on what’s happening here I’d appreciate it.  Anyway, rename the file to FrameRateLibraryTestMain.mm and it should build happily.


int main(int argc, char *argv[])
{
    return 0;
}

Next step is to link in UnitTest++ library and add a simple test.  Change FrameRateLibraryTestMain.mm by adding the code below.  Select the FrameRateLibraryTest target, and click the info button (big blue button with an ‘i’ in it).  Add libUnitTest++.a both as a dependancy and a linked library.


#include  "UnitTest++.h"

TEST(FailSpectacularly)
{
    CHECK(false);
}

int main(int argc, char *argv[])
{
    return UnitTest::RunAllTests();
}

Now let’s test what we have.  From the project menu choose “Set active target…” and set it to be our FrameRateLibraryTest target.  Build and Go, and the iPhone Simulator should pop up briefly, but more importantly, if you look at the console output (you may need to switch to debugging view), you’ll see that our tests ran, and failed spectacularly, as we would expect.  Hurrah!  Note, that clicking on the error takes you straight to the test case that has failed.

Failure, excellent!

Failure, excellent!

So, to recap.  We have a separate application that runs tests, which is linked to the UnitTest++ static library.  We can run the application to run our test code.  We have two things left to do:

  1. Set up our FrameRateLibrary library, and tests for it, and
  2. Run the tests as part of the main (CoolGame) target’s build process.

Let’s tackle the second of those problems first.  Switch the active target back to CoolGame (from the Project menu).  Control click on CoolGame target, and choose ‘Add new build phase | New Run script’.  Get info on the run script phase, either by clicking the ‘i’ button as we did before, or control clicking on the CoolGame target and choosing ‘Get Info’.  In the info dialog, add:

$TARGET_BUILD_DIR/FrameRateLibraryTest.app/FrameRateLibrary

Build and go now, and this fails, but not in the way we want.  This problem caused me some grief until I realised that the problem was my script trying to run an iPhone application as a Mac one.  Oops.  What we actually need is to get the iPhone simulator to run the test application for us.  After much gnashing of teeth (and some of googling), I found a description of what to do on CocoaWithLove, using a trick from the Google Toolbox for Mac.  So the final script ends up looking like:


export DYLD_ROOT_PATH="$SDKROOT"
export DYLD_FRAMEWORK_PATH="$CONFIGURATION_BUILD_DIR"
export IPHONE_SIMULATOR_ROOT="$SDKROOT"
export CFFIXED_USER_HOME="$USER_LIBRARY_DIR/Application Support/iPhone Simulator/User"
$TARGET_BUILD_DIR/FrameRateLibraryTest.app/FrameRateLibraryTest -RegisterForSystemEvents

If you try to build now, the build should fail (in a good way), pointing to the test that’s (deliberately) failing.  This is a good thing!  Let’s ‘fix’ the test by removing it in FrameRateLibraryTestMain.mm, and next time we build we’ll get a nice green tick.  Note, you may need to ‘get info’ on the CoolGame target, and add FrameRateLibraryTest as a dependency, otherwise, when you change the code in FrameRateLibraryTestMain.mm, it won’t recompile it when you build the CoolGame application.

OK, one last thing to do, let’s actually put in place a useful test of our static library.  Get Info on FrameRateLibraryTest, and add FrameRateLibrary as both a linked library and a dependancy.  Let’s make out test code for the library. As a proof of concept, let’s create a test that asks the library for our application’s frame rate – 60 frames per second in this case.  Add the following to FrameRateLibraryTestMain.mm:


#include "FrameRateLibrary.h"

TEST(CheckFrameRate)
{
     CHECK_EQUAL(60, GetFrameRate());
}

With our test done, we can write the code that it’s testing.  Add a FrameRateLibrary.cpp file (and .h) to the FrameRateLibrary group  In the dialog that pops up, choose {YourProjectPath}/CoolGame/FrameRateLibrary as the location to create the files and ensure they’re added to the FrameRateLibrary target.

Add the following to FrameRateLibrary.h:


extern int GetFrameRate();

and FrameRateLibrary.cpp:


#include  "FrameRateLibrary.h"

int GetFrameRate()
{
    return 30;
}

Note, in the best traditions of TDD, I’ve set up the test to fail before it succeeds.  This is to prove the test is actually running correctly and is an essential step.  Try building now, and the build fails, highlighting the test case that is broken.  Let’s fix the GetFrameRate() function to return 60, not the 30 I ‘accidentally’ put in there.  build now, and we should be all happy again.

Well almost.  One last thing is to link our FrameRateLibrary into the actual application we’re creating.  This is fairly simple and uses the techniques we’ve seen several times already (adding the FrameRateLibrary to our target as a dependancy and a linked library).  When I did this though, the build failed with an unresolved symbol error for GetFrameRate().  The place I’d used the GetFrameRate() function was in CoolGameAppDelegate.m – which was being compiled as pure Objective-C due to the .m suffix.  Renaming the file to CoolGameAppDelegate.mm fixed the problem.

Phew!  This was a long post, but we’ve achieved a lot.  We can set up static library targets, test them and run the tests automatically as part of the build process.  It seems like a lot of work, but in fact it can be set up fairly quickly.  Adding UnitTest++ target and code as part of a project template would make the setup signifcantly easier.  I’m still learning the ins and outs of iPhone / XCode development as I go, so please feel free to point out areas where I could do things a little bit better.  Full source code for this tutorial can be found here.

Share with friends:
  • Print
  • email
  • RSS
  • Facebook
  • Twitter
  • Google Bookmarks
  • Digg
  • Reddit
  • StumbleUpon
  • del.icio.us

No related posts.

:, , ,

7 Comments for this entry

  • Noel

    Nice article, George! It’s a perfect step-by-step description for running unit tests on iPhone projects.

    Things that I want to do but I’m not currently doing include:
    - Running unit tests on the iPhone as well (right now they’re #ifdef’d out)
    - Use UnitTest++ for Objective C code as well

    If someone is doing that, I’d love to hear about it :-)

  • Julian

    Excellent article :) I went through a similar process – it seems most people don’t talk about unit testing on the iPhone itself (other than running from the main app).

    And like Noel, I would love to have a solution to this. Perhaps Applescripting XCode is the answer here?

  • Harry

    This article is a good start but it is not thorough. I was unsuccessful following the above steps to integrate UnitTest++ into XCode 3.2.1. Is it possible to add screen shots to each step. I am getting 2788 errors. The link to CoolGame xcode project builds with one error “Code sign error”.

    Any help in getting a empty UnitTest++ project working with Xcode 3.2.1 is greatly appreciated,

    Thanks,
    Harry

  • George

    Hi Harry,

    I haven’t yet had a chance to try this code under 3.2.1 – I’ve only just recently upgraded. I don’t think a difference in XCode versions is the problem here.

    I’ll try to help you with the trouble you’re having, could you help me by supplying a little more detail? For example:

    - Does the sample project (link near the top of the article) work for you?
    - There are a few stages through the article where it says you can try things to check they are working. At what stage does it stop working?
    - I’m not sure off hand what a 2788 error is, can you supply the text that goes with that error?
    - Also, a ‘Code sign error’ sounds like a badly setup provisioning file. Are you building to a device or the simulator?

  • Ken

    Hi George,

    Thanks for this article, but I am running into a problem. I was getting 997 compile errors, and then I renamed the main .cpp to .mm. Those errors went away and I was left with about 10 link errors. I realized I had not marked the files in UnitTest++ as belonging to the new UnitTests target I added.

    After adding that in, I am back to the 997 errors. It appears to be related again to the fact that the UnitTest++ files are .cpp files.

    Does anyone know what is wrong with this? I tried commenting out the contents of the prefix file (precompiled header), and removing the file from the project, but the results were the same.

    It has something to do with including the UIKit framework in a precompiled header, but I cannot figure out where the issue is.

  • George

    Hi Ken,

    The problem here is that you need the files to be compiled using both Objective-C and C++. By default a .m is compiled as just Obj-C, and .cpp as C++. .mm files allow both Obj-C and C++ code.

    In this case, the iPhone Sdk files make use of Objective-C, so any C++ files that include them will need to be renamed .mm

    I haven’t looked at this code recently, but if you include UIKit.h in your pre-compiled header, you’ll need to make your .cpp files into .mm (or find a way to not use the pre-compiled headers with those files). As an alternative, does UIKit.h need to be included in every single file? Could you instead just include it in those .m files that make use of it?

  • Ken

    Hi George,

    Sadly I was just too quick to gloss over the steps you wrote, thinking that I knew what I was doing more or less. :-(

    After re-reading the above CAREFULLY, I realized that I had just added the UnitTest++ code directly to the target instead of making a separate .a file and linking to it.

    XCode was trying to compile it all together directly.

    My first test is compiling and failing now, as it should.

    Thanks again for the article!

3 Trackbacks / Pingbacks for this entry

Leave a Reply