差分表示


*&date(Y-n-j[lL],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桁以上大きくなってしまったけれど.

[[[これ>http://www.6809.net/tenk/html/lib/unitst.zip]]]
[[[これ>http://www.6809.net/tenk/html/sbr/unitst.zip]]]

//-1テストの管理は関数へのポインタ以外に名前やエラー数等確保するようになったため、使用メモリが増えた(けど、許容範囲だろう)
//-ターゲット環境で使う可能性もあるので、std::stringやiostream系は使わず、例外の使用もマクロでon/off.
//-名前はUNITTESTは一般的すぎかと思い UNITST に変更.けど、あんまりよくないネーミングだったかも...gtestみてたらUNITTESTでも問題ないように気にもなり.
//-チェック用の各種マクロは他のテストフレームワークのモノを適当に参考にし...けど結局名前はあわせられず.
//ただ型名別にマクロ用意してるので多少面倒...
//で、不精してstrstreamを使うモードも用意してみたり

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

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

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

----
#comment