差分表示
*&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