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).
Recently I’ve been playing around with Boost’s Test library — in particular, the Unit Test Framework. Here’s a quick guide on how you can integrate it into a project in Xcode, Apple’s IDE.
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.
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.
// Tests for the 'b' class. BOOST_AUTO_TEST_SUITE(b_tests) BOOST_AUTO_TEST_CASE(subtract_tests) { // Ensure that subtracting 6 from 8 gives 2. BOOST_CHECK_EQUAL(b::subtract(8, 6), 2); } BOOST_AUTO_TEST_SUITE_END()
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:
#define BOOST_TEST_DYN_LINK #define BOOST_TEST_MODULE my application tests #include <boost/test/unit_test.hpp>
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):
"${TARGET_BUILD_DIR}/${EXECUTABLE_NAME}"
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.
struct xcode_log_formatter : public boost::unit_test::output::compiler_log_formatter { // Produces an Xcode-friendly message prefix. void print_prefix(std::ostream& output, boost::unit_test::const_string file_name, std::size_t line) { output << file_name << ':' << line << ": error: "; } };
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.
// Set up the unit test framework to use an xcode-friendly log formatter. struct xcode_config { xcode_config() { unit_test_log.set_formatter( new xcode_log_formatter ); } ~xcode_config() {} }; // Call our fixture. BOOST_GLOBAL_FIXTURE(xcode_config);
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.