UnitTestFramework CUnitの使い方(2/2)
テスト・プロジェクト
まずコンソール・アプリケーションのひな型を作り、テスト対象となるソース/ヘッダを追加します。次にプロジェクト・プロパティでCUnitヘッダのディレクトリとライブラリを設定します。
これで完了。
テストの書き方
CUnitでは複数のテストをまとめたものをsuiteと呼んでいます。CUnitは複数のsuiteを一気に実行してくれます。例えばCar
とBus
それぞれのテストをそれぞれsuiteとし、ふたつのsuiteを登録して実行というような手順となります。
ではテストをいくつか書きましょう。まずはテスト対象を生成/廃棄する初期化/後始末関数を用意します。これらはそれぞれsuiteの開始直前/終了直後に呼び出されます。いずれも引数はなし、戻り値はint
で正常に初期化/後始末できたら0を返してください。
#include <CUnit.h> /* 必ずincludeすること! */ #include "Car.h" Car c; /* 初期化 */ int car_setup(void) { c = car_new(50,2); return c != NULL ? 0 : 1; } /* 後始末 */ int car_teardown(void) { car_delete(c); return 0; }
つぎにテスト関数。テスト関数は引数/戻り値共にvoid
です。
assert
マクロではassert
できるのは「正しく動作していればtrueとなる式」だけでしたが、CUnitは検証のためのさまざまなマクロCU_****
を用意してくれています。
/* 初期化は正しいか? */ void test_car_init(void) { /* 走行距離、ガソリン残量共に0 */ CU_ASSERT_EQUAL(car_distance(c), 0); CU_ASSERT_EQUAL(car_remain(c), 0); } /* 給油できるか? */ void test_car_refuel(void) { car_refuel(c, 40); CU_ASSERT_EQUAL(car_remain(c), 40); /* 最大給油量を超えないか */ car_refuel(c, 20); CU_ASSERT_EQUAL(car_remain(c), 50); }
テストが書けたら、それらをまとめてsuiteを作ります。各テストに簡単なタイトルを付けることができます。
static CU_TestInfo test_car[] = { { "初期化", test_car_init }, { "給油", test_car_refuel }, CU_TEST_INFO_NULL, /* 末尾はCU_TEST_INFO_NULLで終端 */ };
さらにsuiteをまとめます。このときsuiteにタイトルを付け、始めに用意した初期化/後始末関数と共に登録します。
static CU_SuiteInfo suites[] = { { "Carのテスト", car_setup, car_teardown, test_car }, CU_SUITE_INFO_NULL, /* 末尾はCU_SUITE_INFO_NULLで終端 */ };
最後にmain
。CUnitはいくつかのテスト形式をサポートしていますが、ここでは基本的なBasic
を使いましょう。
#include <Basic.h> int main() { CU_initialize_registry(); /* 初期化 */ CU_register_suites(suites); /* suites登録 */ CU_basic_set_mode(CU_BRM_VERBOSE); CU_basic_run_tests(); /* テスト実行 */ CU_cleanup_registry(); /* 後始末 */ return 0; }
ここまでのコード片をまとめて例えば「test.c」とし、プロジェクトに追加します。
#include <CUnit.h> #include "Car.h" Car c; int car_setup(void) { c = car_new(50,2); return c != NULL ? 0 : 1; } int car_teardown(void) { car_delete(c); return 0; } void test_car_init(void) { CU_ASSERT_EQUAL(car_distance(c), 0); CU_ASSERT_EQUAL(car_remain(c), 0); } void test_car_refuel(void) { car_refuel(c, 20); CU_ASSERT_EQUAL(car_remain(c), 20); car_refuel(c, 20); CU_ASSERT_EQUAL(car_remain(c), 40); car_refuel(c, 20); CU_ASSERT_EQUAL(car_remain(c), 50); } static CU_TestInfo test_car[] = { { "初期化", test_car_init }, { "給油", test_car_refuel }, CU_TEST_INFO_NULL, }; static CU_SuiteInfo suites[] = { { "Carのテスト", car_setup, car_teardown, test_car }, CU_SUITE_INFO_NULL, }; #include <Basic.h> int main() { CU_initialize_registry(); CU_register_suites(suites); CU_basic_set_mode(CU_BRM_VERBOSE); CU_basic_run_tests(); CU_cleanup_registry(); return 0; }
てコンパイル/実行します。全テストにパスすれば、次のような出力が得られます。
CUnit - A Unit testing framework for C - Version 2.1-0
http://cunit.sourceforge.net/
Suite: Carのテスト
Test: 初期化 ... passed
Test: 給油 ... passed
--Run Summary: Type Total Ran Passed Failed
suites 1 1 n/a 0
tests 2 2 2 0
asserts 5 5 5 0
給油関数car_refuel
にわざと誤りを混入させ、タンク容量制限を外してみました。すると…
CUnit - A Unit testing framework for C - Version 2.1-0
http://cunit.sourceforge.net/
Suite: Carのテスト
Test: 初期化 ... passed
Test: 給油 ... FAILED
1. .\test.c:27 - CU_ASSERT_EQUAL(car_remain(c),50)
--Run Summary: Type Total Ran Passed Failed
suites 1 1 n/a 0
tests 2 2 1 1
asserts 5 5 4 1
「test.c」の27行目で失敗したことを教えてくれていますね。
まとめ
CUnitを使った開発の手順はおおむね次のようになります。
- 関数のインターフェイスを定め、ヘッダに追加する。
- 関数の仕様と定義されたインターフェイスを基にテストを書く。
- 定義されたインターフェイスに従った実装を書く。このとき関数内はほとんど空でいい。
- コンパイル/リンクし、テスト(実行)する。空の関数を呼び出すため、おそらくテストは失敗する。それによってテストされていること/関数が呼び出されていることを確認する。
- テストにパスするよう、関数を実装する。
この手順を繰り返し、考えうるすべてのテストにパスすれば終了です。
こうやって作られたテストは大切に保存しておきましょう。このテストコードは提供された機能のすべてを実際に呼び出す、優れたサンプル(利用マニュアル)でもあるのです。後日機能の変更や拡張が必要となったとき、まずテストして元コードが正しく動作することを確認します。そして拡張/変更に基づきテストに追加/変更を加え、全テストをパスするようコードに手を加えます。
常にテストされた状態を維持しながらコードの修正を行うことが重要なのです。そうすることであなたが書いたコードの信頼性/堅牢性を確保できるのですから。