Tag: Static Libraries
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.
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:
- Set up our FrameRateLibrary library, and tests for it, and
- 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.
