[ C++で開発 ]

ユニットテスト by CppUnit

CppUnitを使ってユニットテストを行います。ここでいうユニットテストとは、関数やクラスのメンバ関数を対象とする単体テストのことで、テストケースとして所定の入力を与えて関数(メンバ関数)を実行し、実行結果である出力を所定の入力に対する期待値と比較照合して良否を判断するものです。

環境設定

CppUnitを使うための環境構築を行います。CppUnitは、ソースコードで配布されているので、標準C++規格に適合するコンパイラであればおそらく環境が構築できると思います。

ここでは、Solaris OS上のSunStudio C++コンパイラ、Windows OS上のVisual C++コンパイラ、Linux(CentOS)上のGNU C++コンパイラ環境における構築を紹介しています。(各バージョンは本文参照)

ソースコードの入手

次のサイトからソース一式(tarball)をダウンロードします。ソース一式はプラットフォームによらず共通です。

2008.4.19現在、1.12.1 が最新版です。

ビルド

ビルドはプラットフォームによって異なります。

Solaris 9 x86上でSun Studio 9を使用

ソースから、configure/make/make install でインストールします。

work$ tar xzf cppunit-1.10.2.tar.gz
work$ cd cppunit-1.10.2
cppunit-1.10.2$ ./configure CC=cc CXX=CC CXXFLAGS="-pta -instances=static 
 -mt -xtarget=pentium3 -g -features=no%transitions -xildoff" LD=CC 
 LDFLAGS=-xildoff
    :
cppunit-1.10.2$ make
    :
cppunit-1.10.2$ su
cppunit-1.10.2# make install
    :
cppunit-1.10.2$ exit

インストールされると、デフォルトでは/usr/local以下に展開されます。

ヘッダーファイル群 /usr/local/include/cppunit
ライブラリファイル群 /usr/local/lib直下

Solaris 10 x86上でSun Studio 11を使用

cppunit-1.12.0のINSTALL-unixに記載されているSolaris用のビルド記述は少し古いので、そのままではビルド時にエラーが発生します。

work$ tar xzf cppunit-1.12.0.tar.gz
work$ cd cppunit-1.12.0
cppunit-1.12.0$ ./configure CC=cc CXX=CC CXXFLAGS="-pta -instances=static 
 -mt -xtarget=generic -g -features=no%transitions -xildoff" LD=CC LDFLAGS=-xildoff
    :
cppunit-1.12.0$ make
    :
CC -G -zdefs -nolib -hlibcppunit-1.12.so.0 -o .libs/libcppunit-1.12.so.0.0.0 
.libs/AdditionalMessage.o .libs/Assserter.o .libs/BeOsDynamicLibraryManager.o 
.libs/BriefTestProgressListener.o ...
.libs/Win32DynamicLibraryManager.o -mt
未定義の                              最初に参照している
シンボル                                   ファイル
std::ostream &std::ostream::operator<<(int) .libs/CompilerOutputer.o
std::ostream &std::ostream::operator<<(double) .libs/StringTools.o
fabs                                  .libs/TestAssert.o
void __Crun::pure_error()            .libs/CompilerOutputer.o
    :
cppunit-1.12.0$

原因は、-zdefsオプションが指定されている、すなわち動的リンクライブラリを作成時に未定義シンボルがあるとエラーとするようにしていながら、-nolibオプションが指定され、標準では自動的にリンクされているlibCstd、libCrun、libc、libmがリンクされないので、未定義シンボルでエラーが出まくっています。

そこで、configure時に少々オプション定義を変更します。ついでにインストール先ディレクトリも変更します。/usr/localの下ではなく、/opt/cppunitとし、また、SunStudio C++コンパイラ用とGCC C++コンパイラ用と2種類のバイナリに対応できるよう、libディレクトリの下にsc、gcのサブディレクトリを用意します。

cppunit-1.12.0$ ./configure -prefix=/opt/cppunit -libdir=/opt/cppunit/lib/sc \
 CC=cc CXX=CC CXXFLAGS="-pta -instances=global -mt -xtarget=generic -g \
-features=no%transitions" LD=CC LDFLAGS="-lCstd -lCrun -lc -lm" 
    :
cppunit-1.12.0$ gmake
    :
cppunit-1.12.0$ gmake install
    :
cppunit-1.12.0$

Windows XP SP2上でVisual Studio .NET 2003を使用

ソースから、VC++6用のワークスペースファイルCppUnitLibraries.dswを読み込み変換してからバッチビルドを行います。

以下のプロジェクトファイルがあります。

プロジェクト名 構成 生成物 備考
cppunit
Debug

cppunitd.lib

Release

cppunit.lib

cppunit_dll
Debug

cppunitd_dll.dll
cppunitd_dll.lib

Release

cppunit_dll.dll
cppunit_dll.lib

DllPlugInTester
Debug

DllPlugInTesterd_dll.exe

Debug Static

DllPlugInTesterd.exe

Debug Unicode

DllPlugInTesterud.exe

Release

DllPlugInTester_dll.exe

Release Static

DllPlugInTester.exe

Release Unicode

DllPlugInTesteru.exe

DSPlugIn
Debug
Debug Unicode
Release
Release Unicode
VC++6専用
TestPlugInRunner
Debug

TestPlugInRunnerd.exe

Release
TestPlugInRunner.exe
TestRunner
Debug

testrunnerd.dll
testrunnerd.lib

Debug Unicde

testrunnerud.dll
testrunnerud.lib

Release

TestRunner.dll
TestRunner.lib

Release Unicode

testrunneru.dll
testrunneru.lib

バッチビルドが完了すると、これら生成物はcppunit-1.12.0\libディレクトリ化にコピーされています。そこで、cppunit-1.12.0の下のincludeおよびlibを然るべきところへコピーします。

D:\temp\cppunit-1.12.0> mkdir D:\lib\cppunit
D:\temp\cppunit-1.12.0> mkdir D:\lib\cppunit\include
D:\temp\cppunit-1.12.0> mkdir D:\lib\cppunit\lib
D:\temp\cppunit-1.12.0> xcopy include D:\lib\cppunit\include /S /E
D:\temp\cppunit-1.12.0> xcopy lib D:\lib\cppunit\lib /S /E
D:\temp\cppunit-1.12.0> 

Linux(CentOS 5.1)上でGCC4.1.2を使用

tarballにcppunit.specファイルが含まれているので、これをベースに修正を行ってRPMパッケージを作成します。

ビルド手順の詳細は、CentOS 5バイナリ・パッケージの記事を参照してください。

インストール先は、システム・インクルード・ディレクトリ(/usr/include)およびシステム・ライブラリ・ディレクトリ(/usr/lib)になるので、ビルド時のオプション指定は最小限で済みます。

CppUnit使用プログラムのコンパイルとリンク

Solaris 9 x86 Sun Studio 9

コンパイルオプション指定

work$ CC -I/usr/local/include -c SimpleTest.cc
    :

リンクオプション指定

ダイナミックリンク時の例
work$ CC -L/usr/local/lib -o SimpleTest SimpleTest.o -lcppunit
work$ ./SimpleTest
ld.so.1: ./SimpleTest: 重大なエラー: libcppunit-1.10.2.so.2: open に失敗しました:
 ファイルもディレクトリもありません。
強制終了
work$ export LD_LIBRARY_PATH=/usr/local/lib
work$ ./SimpleTest
    :
work$
スタティックリンク時の例
work$ CC -L/usr/local/lib -o SimpleTest SimpleTest.o -Bstatic -lcppunit
    :

テストコードの記述

詳細は、リンク・情報源を参照ください。ここでは、各種情報源ではカバーできない最新版の情報や、はまり所をメモします。

テスト・クラス

慣習で、テスト対象クラス名+Testと名前を付けますが、分かれば別な名前でも構いません。テストクラスは、CppUnitのTestFixtureクラスを継承して作成するのが一般的です。

ヘッダーファイル(.h)とソースファイル(.cpp)どちらにテストを書くか

C++ではヘッダーファイルとソースファイルの両方をメンテナンスしなければならないので、頻繁にメンバ関数を追加していくテスト駆動開発でのテストクラスはソースファイルだけに記述するのがよいと思います。それに、ヘッダーファイルを設けても誰も使用しないですし。

// MyComponentTest.cpp
#include <cppunit/extensions/HelperMacros.h>

class MyComponentTest : public CPPUNIT_NS::TestFixture {
    CPPUNIT_TEST_SUITE(MyComponentTest);
    // この間にテスト実行メンバ関数を指定するマクロを記述(複数)
    CPPUNIT_TEST_SUTIE_END();
public:
    virtual void setUp() {
    }
    virtual void tearDown() {
    }
};

CPPUNIT_TEST_SUITE_REGISTRATION(MyComponentTest);

上記がテストクラスの最小限の雛形です。
CPPUNIT_NSはCppUnitの名前空間のマクロでおそらくnamespace未対応コンパイラ用にマクロ化されたもの?

テスト・メンバ関数

テストクラスにテスト・メンバ関数を定義するときは、CPPUNIT_TESTマクロも定義します。

  :
class MyComponentTest : public CPPUNIT_NS::TestFixture {
    CPPUNIT_TEST_SUITE(MyComponentTest);
    CPPUNIT_TEST(testPositiveUpperCase);
    CPPUNIT_TEST_SUTIE_END();
public:
    :
    void testPositiveUpperCase() {
        MyComponent compo;
        compo.set(INT_MAX);
        CPPUNIT_ASSERT_EQUAL(compo.get(), INT_MAX);
    }
};
  :

メンバ関数では、テストを実行し、結果をCPPUNIT_ASSERT_EQUALマクロで期待値と比較します。比較結果がテスト結果として記録されます。

テスト・ランナー

テストクラスを実行し結果を出力するメインプログラムを定義します。

以下は、@ITの記事「C++アプリケーションの効率的なテスト手法(CppUnit編)」(えぴすてーめー氏)で紹介しているテスト・ランナー(CppUnitテストケース実行用メイン関数)です。

// Main.cpp
#include <cppunit/BriefTestProgressListener.h>
#include <cppunit/CompilerOutputter.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TestRunner.h>

int main(int argc, char* argv[]) {
    CPPUNIT_NS::TestResult controller;
    CPPUNIT_NS::TestResultCollector result;
    controller.addListener(&result);

    CPPUNIT_NS::BriefTestProgressListener progress;
    controller.addListener(&progress);

    CPPUNIT_NS::TestRunner runner;
    runner.addTest(CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest());
    runner.run(controller);

    CPPUNIT_NS::CompilerOutputter outputter(&result, CPPUNIT_NS::stdCOut());
    outputter.write();
  
    return result.wasSuccessful() ? 0 : 1;
}

テストクラスでCPPUNIT_TEST_SUITE_REGISTRATIONマクロにより登録されたテストケースを実行する仕組みになっており、メイン関数から直接テストケースを呼び出す部分はありません。

ただし、テスト・ランナーのコンパイル・リンク時に、テストクラスが含まれるライブラリをリンクする必要があります。

動的にテストクラスを含むライブラリをロードして実行

テスト・ランナーのコードはテストクラスが変わっても修正不要ですが、リンクをし直す必要があります。そこで、実行時に文字列で指定されたテストクラスのライブラリファイルをロードするように修正し、テストクラスのライブラリを自由に指定できるようにしてみました。コードはLinux用ですが、ライブラリの動的ロードはWindowsでも可能です。

以下に、上記テスト・ランナーのコード(Main.cpp)に追加する部分だけ抜粋して示します。

#include <dlfcn.h>

int loadLibrary(const char* filename) {
    void* handle = dlopen(filename, RTLD_NOW|RTLD_GLOBAL);
    if (handle == 0) {
        std::cerr << "[Error]dlopen failed for " << filename << std::endl;
        return -1;
    }
    return 0;
}  

int main(int argc, char* argv[]) {
    if (argc <= 1) {
        std::cerr << "no test case library specified in argument" << std::endl;
        return -1;
    }

    for (int i=1; i<argc; i++) {
        loadLibrary(argv[i]);
    } 
   :(略)

テスト・ランナー

テストを実行し結果を出力するメイン・プログラムをテスト・ランナーと呼びます。CppUnitでは、テスト・ランナーもアプリケーション開発者が記述する必要があります。

CppUnit標準で提供されるテキストUIに加え、いくつかのGUIライブラリ用テスト・ランナーも公式・非公式に提供されています。MFC, Qt 2.x, Qt 4.x, Curses, WxWindows など。

Qt 4用GUIテスト・ランナー QxTestRunner

QxRunnerと呼ばれるQt 4.x用のテスト・ランナー構築用ライブラリがあります。

インストール

2008年5月5日現在の最新版は0.9.2です。ソースアーカイブqxrunner.0.9.2.tar.gzをダウンロードします。

2つのライブラリ qxcppunit と qxrunner をビルドします。付属のREADME.txtには、VisualStudio2005およびLinux用のビルド手順が記載されています。以下Linux(CentOS 5、Qt 4.3.4、GCC4.1.2)における手順を示します。

Linuxでのビルドとインストール

ビルドするライブラリ種類として静的リンクライブラリ、動的リンクライブラリを指定できます。また、デバッグビルド、リリースビルドを指定します。ユニットテスト環境なので、ここでは動的リンクライブラリのデバッグビルド版をビルドします。

work$ tar xvzf qxrunner.0.9.2.tar.gz
    :
work$ cd qxrunner
qxrunner$ cd src/qxrunner
qxrunner$ qmake qxrunner.pro "CONFIG+=debug_dll"
    :
qxrunner$ make
    :
qxrunner$ cd ../qxcppunit
qxcppunit$ qmake qxcppunit.pro "CONFIG+=debug_dll"
    :
qxcppunit$ make
    :
qxcppunit$ cd ../../lib
lib$ ls
libqxcppunitd_shared.so          libqxrunnerd_shared.so
libqxcppunitd_shared.so.1        libqxrunnerd_shared.so.1
libqxcppunitd_shared.so.1.0      libqxrunnerd_shared.so.1.0
libqxcppunitd_shared.so.1.0.0    libqxrunnerd_shared.so.1.0.0
lib$

インストール先は、RPMパッケージ化していないので、/usr/localの下とします。

# cd work
# mv qxrunner /usr/local/
#

テスト・ランナーの記述

// QtMain.cpp
#include <QApplication>
#include <qxcppunit/testrunner.h>
#include <cppunit/extensions/TestFactoryRegistry.h>

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);
    QxCppUnit::TestRunner runner;
    runner.addTest(
        CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest());
    runner.run();

    return 0;
}

リンク・情報源

書籍

Web

日本語サイト