2010-2-13[土] unittestの作成メモ

D言語の unittest に触発されて、 C++用に真似たものを作ってみたことがある.

使い方はこんな感じ

// テスト例
#include "unittest.h"

class Foo { 略 };

UNITTEST(Foo_Test) {
    Foo foo;
    assert(foo.hoge() == 1);
    略
}

// 起動ルーチン
#include "unittest.h"

int main() {
    UNITTEST_RUN_ALL();
    略
}

  • unittest.hをincludeして
  • UNITTEST()でテストを書き
  • main()の初っ端で UNITTEST_RUN_ALL() を呼び出す.
  • テスト実行の有無は、コンパイラオプションでのUSE_UNITTESTマクロの定義の有無

といった感じでヘッダファイル一つですむ. そのヘッダの中身は

#ifndef UNITTEST_H
#define UNITTEST_H
#include <cassert>

#ifndef UNITTEST_MAX
 #define UNITTEST_MAX   256     ///< 登録できるテストの数
#endif

#ifndef USE_UNITTEST            // テストしないとき.

#define UNITTEST_RUN_ALL()
#define UNITTEST(T)     template<typename D> void uniTTesT_dmy##T()

#else   // テストするとき.

#define UNITTEST_RUN_ALL()  uNITtEST_Mgr<>::runAll()
#define UNITTEST(T)                             \
    class T {                                   \
    public:  T() {uNITtEST_Mgr<>::add(&test);}  \
    private: static void test();                \
    };                                          \
    static T uNITtEST_vAr_##T;                  \
    void T::test()

template<int N=UNITTEST_MAX>
class uNITtEST_Mgr {
    static unsigned tblNum_;
    static void*    tbl_[N]; //関数ポインタの配列だとvcが落ちたorz.
public:
    static void add(void (*fnc)()) {
        if (tblNum_ < N)
            tbl_[tblNum_++] = (void*)fnc;
        else
            assert(0 && "too many UNITTEST.");
    }

    static void runAll() {
        for (unsigned i = 0; i < tblNum_; i++)
            ((void (*)())tbl_[i])();
    }
};
template<int N> unsigned uNITtEST_Mgr<N>::tblNum_=0;
template<int N> void*    uNITtEST_Mgr<N>::tbl_[N];

#endif  // USE_UNITTEST
#endif  // UNITTEST_H

テストしないときは、
UNITST_RUN_ALL()マクロは 空なので main()で何も実行されず、
UNITST_TEST(T) マクロは 関数テンプレートを定義するが 使われず実体化しないので、コードは生成されない。

テストをするときは、
uNITtEST_Mgrクラスが定義されてる。
こいつはstaticメンバーだけで構成していて、 テスト関数へのポインタの配列とその数、 テスト関数をポインタ配列に登録するadd()関数、 登録された関数をすべて実行するrunAll()関数、 がある.

UNITST_TEST()マクロは、ファイルstaticのclass変数を定義にすることで main()実行前にコンストラクタが実行され、 そのコンストラクタ内でテスト関数へのポインタを UnitstMgr::add()で登録している.

グローバル変数のコンストラクタの実行順番は不定なので、Mgrの管理する変数はそれらよりも先に初期化されている必要がありstatic変数にしている(ので登録可能なテスト数/配列サイズも固定)

そして main()関数の開始時点で、uNITtEST_Mgrにテストがすべて 登録済みなので、UNITST_RUN_ALL ( UnitstMgr::runAll ) を実行すれば、テストが全て実行される.

わりと単純にすむ、というか、すませてる.
main()で1文とはいえテスト用に呼出す必要あるし、 テストの最大数を決め打ちする必要があるのは無粋だけど.


まあ、実際に使おうとすると、assertだけでなく、各種チェック用のマクロがそこそこほしくなるし、多少なりとはいえ、経過メッセージをだしたりエラー数カウントしたりしだすと、すぐ1桁以上大きくなってしまったけれど.

[これ]


こんな "オレ仕様" でなく CppUnit なり Google の gtest なり boost のモノなりを 使い慣れたほうが生産的だろうけれど、 慣れる以前に面倒そうで手が出せず、 こんなものを作ってしまった、と (ターゲット環境で使えるものがほしかったてのもあり)

ただ色々付け加えた後に、ひさしぶりに元の(上記の)ソースみると、 蛇足だったかなあ、という気にもなる.

※そういや、グローバルのコンストラクタって処理順保証なしだから マルチスレッドでばらばらに実行される可能性ってあるのかな? (当然そうだと今のままでは破綻)