PCUnitでテスト関数を自動登録するスクリプトを作った

TDD Boot Camp 東京 1.6にて、C言語チームにユニットテスト ツールとしてPCUnitを使っていただいたそうです。

お勧めしてくれた[twitter:@mayonezudaiou]さん、ありがとうございます!
プログラマーとして自分が作ったものを使ってもらえるということは本当にうれしいことですね。

TDDツールとしての感想をいただきました。
http://d.hatena.ne.jp/do_aki/20110807/1312724116
http://d.hatena.ne.jp/teyamagu/20110731/p1

テスト関数を手動で登録しなければならないというのを忘れがちになってしまう、あるいはめんどくさいというお言葉。

CUnitもCppUnitも手動で登録しなければならないのは同じなんだけど、確かに手動登録は面倒だ。
Cutterは手動登録しなくてもいいらしい。

PCUnitは組み込み用のターゲットにも使えるようにシンプルな作りなので、やはりソースのどこかにテスト関数の関数ポインタを登録しなければならない。

どうしようかと考えたところ、ソース内のtestで始まるテスト関数の定義を検索して、その中から登録されていないテスト関数を自動で書き込んで登録するようなスクリプトを作って、テストプロジェクトのビルド時に実行するようにすればいいんじゃないか、と思った。エディタで編集中のファイルを書き換えられるのはちょっと気持ち悪いしエディタがロックかけてたらまずい気がするけど気にしないでおこう。

ファイルを読み出して文字列処理をしてまた書き戻すなんてことは、スクリプト言語でやるのが楽なのでRubyで作った。ついでにテストのソースの雛形を生成するスクリプトも作った。
この2つを使えばテストコードについてはプログラマーはテスト関数を書くだけでよくなる。お約束の部分のコードをコピペしなくてもいい。

雛形生成スクリプト(pcunit_template.rb)

pcunit_template.rbの使い方は、引数にテストスイート名を1つ以上指定すると、テストスイート名.cというファイルを生成する。-pオプションを指定すると拡張子がcppになる。-mオプションを指定するとPCU_runを呼び出すmain関数を定義したmain.c(-pならmain.cpp)というファイルを生成する。

例:

$ pcunit_template.rb HogeTest PiyoTest -p -m

を実行すると、HogeTest.cpp, PiyoTest.cpp, main.cppを生成する。

自動登録スクリプト(pcunit_register.rb)

pcunit_register.rbは、テストプロジェクトのソースファイルのあるディレクトリで引数なしで実行すると、再帰的に全てのソースコードをチェックして未登録のテスト関数の登録をする。
また、main関数での各スイートメソッドの登録も自動で行う。
オプションに-d "ソースのディレクトリ"を指定すると指定したディレクトリ以下のソースを対象とする。

具体的にどのような処理を行うか例をあげると、以下のようにtest_hogeのテスト関数の定義はあるけどPCU_Testの配列に登録されていない場合、

static void test_hoge(void)
{
    ...
}

PCU_Suite *HogeTest_suite(void)
{
    static PCU_Test tests[] = {
    };
    static PCU_Suite suite = { "HogeTest", tests, sizeof tests / sizeof tests[0] };
    return &suite;
}

pcunit_register.rbを実行すると、PCU_Testの配列の初期化が追加される。

PCU_Suite *HogeTest_suite(void)
{
    static PCU_Test tests[] = {
        PCU_TEST(test_hoge),  ←ここに追加
    };
    static PCU_Suite suite = { "HogeTest", tests, sizeof tests / sizeof tests[0] };
    return &suite;
}

また、以下のようにスイートメソッドを登録していないmain関数は、

int main(int argc, char **argv)
{
    const PCU_SuiteMethod suites[] = {
    };
    PCU_run(suites, sizeof suites / sizeof suites[0]);
    return 0;
}

このようにスイートメソッドのプロトタイプ宣言とPCU_SuiteMethodの配列の初期化が追加される。

PCU_Suite *HogeTest_suite(void);  ←ここに追加

int main(int argc, char **argv)
{
    const PCU_SuiteMethod suites[] = {
        HogeTest_suite,  ←ここに追加
    };
    PCU_run(suites, sizeof suites / sizeof suites[0]);
    return 0;
}

pcunit_register.rbをビルド時に自動実行させる方法

ビルド時にスクリプトを自動で実行させるようにできれば、ビルドするだけで自動的にテスト関数が登録されるのでプログラマーは登録忘れなんか気にせずにPCUnitに関してはテスト関数だけを書けばいい。

Makefileの場合

allターゲットにて、以下のように$(TARGET)の前にスクリプト実行用ターゲットを記述すれば、make実行でコンパイル前に必ずpcunit_register.rbを実行するようになる。
Makefileがソースファイルと違うディレクトリにある場合はpcunit_register.rbのオプションに-d "ソースのディレクトリ"を指定すること。

- all: $(TARGET)
+ all: pcunit_register $(TARGET)
+ 
+ pcunit_register:
+ 	pcunit_register.rb
Visual C++の場合

プロジェクト→XXXのプロパティ→構成プロパティ→ビルドイベント→ビルド前イベントのコマンドライン

(rubyのパス)\ruby.exe (pcunit_registerのパス)\pcunit_register.rb

と入力する。
Makefileの場合と同様に適宜オプションの-d "ソースのディレクトリ"を指定すること。

ルネサスHEWの場合

ビルド→ビルドフェーズ→ビルド順序タブで追加ボタンを押すと新規ビルドフェーズダイアログが出る。
1/4ステップ 新規カスタムフェーズの作成で次へ
2/4ステップ 単一フェーズを選択して次へ
3/4ステップ フェーズ名は適当にpcunit_registerと指定。コマンドに(rubyのパス)\ruby.exeを指定。デフォルトオプションに(pcunit_registerのパス)\pcunit_register.rbを指定。初期ディレクトリにソースのディレクトリを指定。
4/4ステップ 何も入力せずに完了
追加したビルドフェーズpcunit_registerを一番上に持ってくる。