Warning: this article is no longer being actively updated and parts of it are probably out of date. Proceed with caution!
Update (Nov 2009): the custom log formatter in this article is now part of the Boost Unit Test Library, via the compiler_log_formatter. So you don't need to roll your own anymore! (Thanks Sean for the tip)
Update 2 (Feb 2010): Xcode 3.2 expects a slightly different syntax for errors. I have updated my example below (courtesy of Nicola Vitacolonna) to match, but Boost.Test's built-in compiler_log_formatter is currently awaiting a fix.
When writing C++ code, I frequently use the Boost C++ libraries, which includes all sorts of great libraries to complement the C++ Standard Template Library (STL).
You'll need Xcode 3 and Boost 1.35 installed, and a C++ project to play with. For this example, I'm using a C++ Command Line Utility project, which you can download here.
Add a target for the tests executable
First, let's create a new target for the executable that will run all our tests.
Right click on Targets, and click Add > New Target. Select Shell Tool from the list. Use "Tests" for the target's name.
On the Build tab of the Target Info window, add Boost's install paths to the Header and Library Search Paths. On this machine, I used MacPorts to install Boost, which uses /opt/local/include and opt/local/lib, respectively.
Right click on the Tests target, and click Add > Existing Frameworks. Browse and select libboost_unit_test_framework-mt-1_35.dylib from your library search path. Add it to your Tests target.
Now the Boost unit test framework is ready to be used from your Xcode project. Let's use it!
Writing some test suites
My application has two classes, called a and b. They both have bugs. I've created a new group called Tests, and written a simple test suite for each of them.
Here's my b_tests.cpp file. The examples I've used aren't important, but the case and suite declarations are.
First, make sure all your .cpp files are added to the Tests target. Then, to tie all the test files together into a single executable, we'll use the test framework's automatic main() feature in our own file called tests_main.cpp:
Set the active target to Tests. To check everything got wired up correctly, open the debugger window and hit Build and Go. You should see a bunch of tests fail.
Running tests as part of the build process
Our tests are set up, so let's integrate them into the build process. Right-click on the Tests target, and click Add > New Run Script Build Phase. Paste the following into the script field (this will resolve to the tests executable):
To keep things obvious, I renamed our new Run Script phase to Run Tests.
Now let's set up a new rule - the main target should only build after tests have been built and run successfully. Right-click on the MyApp target, click Get Info, and add Tests to the Direct Dependencies list.
Change the active target to MyApp, and hit Build. The tests should fail and return an error code, which Xcode will pick up as a script error.
If the tests don't succeed, the build will fail, which is exactly what we want.
Parsing the test results
So far, we've got the tests running, and integrated into the build process, but the test results output are a bit rough. Let's fix that.
Xcode will automatically parse script output if it's prefixed the right way. Unfortunately, none of the Boost Unit Test Framework's run-time parameters can produce this format. So, we're going to have to roll up our sleeves and write our own custom unit_test_log_formatter instead.
I've chucked this in a file called xcode_log_formatter.hpp in the Tests group. In tests_main.cpp, we'll use a global fixture to tell the unit test framework to use our Xcode formatter instead of the default.
After we've got this wired up, the test results look much better:
As you can see, instead of reporting it as a generic script error, Xcode has now recognised both test failures as two individual errors. And instead of hunting around through different files trying to find the line where a test broke, you can simply click on an error, and Xcode will find and highlight it for you.
This makes for much easier development, and allows you to manage unit test failures as easily as standard compile errors.