boost の Unit Test Framework を使ってみた。

日本語の情報が少なくて、色々とはまったのでメモ。
使用したバージョンは 1.40です。たぶん、1.37以降では使えると思います。環境はWindowsなので、Visual Studio 2008を使用しています。

インストール

http://www.boostpro.com/download からインストーラをDownloadしてきます。メールアドレスなどの登録が必要ですが、Freeです。
インストール時に、コンパイルが必要なライブラリのうちインストールするものを聞かれるので、unit test frameworkにチェックを入れておきます。
インストール後、Visual Studioを起動して includeと libディレクトリの設定を行います。ツール→オプション→プロジェクトおよびソリューション→VC++ ディレクトリ の画面で、ディレクトリを表示するプロジェクト→インクルード ファイル を選択し、「C:\Program Files\boost\boost_1_40」を追加*1。同じく、ライブラリ ファイル を選択し、「C:\Program Files\boost\boost_1_40\lib」を追加*2

テストの準備

テスト用に C++で空のプロジェクトを作成します。
以下の2つのファイルを追加します。
これで、Visual Studio上にてDebugで実行すると「出力ウィンドウ」にテスト結果が表示されます。

basic_dostream.h

// via http://www.codeproject.com/KB/debug/debugout.aspx
#pragma once

#include <windows.h>
#include <ostream>
#include <sstream>
#include <string>


template <class CharT, class TraitsT = std::char_traits<CharT> >
class basic_debugbuf : public std::basic_stringbuf<CharT, TraitsT>
{
public:
    virtual ~basic_debugbuf() {
        sync();
    }

protected:
    int sync() {
        output_debug_string(str().c_str());
        str(std::basic_string<CharT>());    // Clear the string buffer
        return 0;
    }
    void output_debug_string(const CharT *text) {}
};

template<>
void basic_debugbuf<char>::output_debug_string(const char *text)
{
    ::OutputDebugStringA(text);
}

template<>
void basic_debugbuf<wchar_t>::output_debug_string(const wchar_t *text)
{
    ::OutputDebugStringW(text);
}


template<class CharT, class TraitsT = std::char_traits<CharT> >
class basic_dostream : public std::basic_ostream<CharT, TraitsT>
{
public:
    basic_dostream() : std::basic_ostream<CharT, TraitsT>
                (new basic_debugbuf<CharT, TraitsT>()) {}
    ~basic_dostream() {
        delete rdbuf();
    }
};

typedef basic_dostream<char>    dostream;
typedef basic_dostream<wchar_t> wdostream;

test_main.cpp

//#define BOOST_TEST_DYN_LINK	// DLLを使用する場合
#define BOOST_TEST_NO_LIB	// staticにリンクする場合

#ifdef _DEBUG
#include <boost/test/unit_test.hpp>
#include <boost/test/unit_test_log.hpp>

#include <iostream>

#include "basic_dostream.h"
dostream trace;

boost::unit_test_framework::test_suite* init_unit_test_suite( int argc, char* argv[] )
{
	// ログの出力先を変更(指定しないとデフォルトではstd::cout)
	boost::unit_test::unit_test_log_t::instance().set_stream( trace );
	std::cerr.rdbuf( trace.rdbuf() );

	return BOOST_TEST_SUITE("replace output stream");
}
#endif

#define BOOST_TEST_MODULE boost_unit_test
#include <boost/test/included/unit_test.hpp>

ユニットテストを書く

例えば、以下のクラスをテストすることにします。

foo.h

#pragma once
#include <exception>

class Foo
{
public:
	int add(int a, int b) const { return a + b; }
	double add(double a, double b) const { return a + b; }
	void throwA() { throw std::exception(); }
};

そのためには、以下のような2通りの書き方ができます。
まずは、単純な場合。
foo_test.cpp

#include <boost/test/unit_test.hpp>

#include "foo.h"

namespace{
	BOOST_AUTO_TEST_SUITE(foo_test_case)	// test suiteの名前

	BOOST_AUTO_TEST_CASE(test_add)	// testの名前
	{
		Foo f;
		BOOST_CHECK_EQUAL(3, f.add(1, 2));	// CHECKでは失敗しても、次の行へ進む。
		BOOST_REQUIRE_EQUAL(4, f.add(1, 3));	// REQUIREの場合には、失敗した場合そこでテストメソッドの実行が終わる。
	}

	BOOST_AUTO_TEST_CASE(test_add2)
	{
		Foo f;
		BOOST_CHECK_CLOSE(3.0, f.add(1.0, 2.0), 1e-6);	// doubleをチェックする場合には、CLOSEで比較を行う。
		BOOST_REQUIRE_CLOSE(4.0, f.add(1.0, 3.0), 1e-6);
	}

	BOOST_AUTO_TEST_CASE(test_throwA)
	{
		Foo f;
		BOOST_CHECK_THROW(f.throwA(), std::exception);	// 例外が投げられるテストを行う場合には、THROWを使う。
	}

	BOOST_AUTO_TEST_SUITE_END()
}

上記の場合には、いわゆる setUpや tearDownに当たる処理が書けないため、必要な場合には以下のようにする。
foo_test2.cpp

#include <boost/test/unit_test.hpp>

#include "foo.h"

namespace{
	struct Fixture {
		Foo* pf;
		Fixture() {
			pf = new Foo();	// setUP
		}
		~Fixture() {
			delete pf;	// tearDown
		}
	};

	BOOST_FIXTURE_TEST_SUITE(new_foo_test_case, Fixture)

	BOOST_AUTO_TEST_CASE(test_add)
	{
		BOOST_CHECK_EQUAL(3, pf->add(1, 2));	// Fixtureで初期化した値が使える。
		BOOST_REQUIRE_EQUAL(4, pf->add(1, 3));
	}

	BOOST_AUTO_TEST_SUITE_END()
}

あとは、テストを増やしたい場合には、foo_test.cppや foo_test2.cppと同様に bar_test.cppなどをプロジェクトに追加すればOKです。

*1:インストールする場所を変更した場合には、そちらを指定

*2:先ほどと同じくインストールした場所に準拠