SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

CUnitによるテスト駆動開発

テストの習慣を身につけましょう!


  • X ポスト
  • このエントリーをはてなブックマークに追加

UnitTestFramework CUnitの使い方(2/2)

テスト・プロジェクト

 まずコンソール・アプリケーションのひな型を作り、テスト対象となるソース/ヘッダを追加します。次にプロジェクト・プロパティでCUnitヘッダのディレクトリとライブラリを設定します。

CUnitヘッダ・ディレクトリ
CUnitヘッダ・ディレクトリ
CUnitライブラリ・ディレクトリ
CUnitライブラリ・ディレクトリ
CUnitライブラリ
CUnitライブラリ

 これで完了。

テストの書き方

 CUnitでは複数のテストをまとめたものをsuiteと呼んでいます。CUnitは複数のsuiteを一気に実行してくれます。例えばCarBusそれぞれのテストをそれぞれsuiteとし、ふたつのsuiteを登録して実行というような手順となります。

 ではテストをいくつか書きましょう。まずはテスト対象を生成/廃棄する初期化/後始末関数を用意します。これらはそれぞれsuiteの開始直前/終了直後に呼び出されます。いずれも引数はなし、戻り値はintで正常に初期化/後始末できたら0を返してください。

suite開始/終了時の関数
#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にタイトルを付け、始めに用意した初期化/後始末関数と共に登録します。

suiteの登録
static CU_SuiteInfo suites[] = {
  { "Carのテスト",  car_setup, car_teardown, test_car },
  CU_SUITE_INFO_NULL, /* 末尾はCU_SUITE_INFO_NULLで終端 */
};

 最後にmain。CUnitはいくつかのテスト形式をサポートしていますが、ここでは基本的なBasicを使いましょう。

main
#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」とし、プロジェクトに追加します。

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を使った開発の手順はおおむね次のようになります。

  1. 関数のインターフェイスを定め、ヘッダに追加する。
  2. 関数の仕様と定義されたインターフェイスを基にテストを書く。
  3. 定義されたインターフェイスに従った実装を書く。このとき関数内はほとんど空でいい。
  4. コンパイル/リンクし、テスト(実行)する。空の関数を呼び出すため、おそらくテストは失敗する。それによってテストされていること/関数が呼び出されていることを確認する。
  5. テストにパスするよう、関数を実装する。

 この手順を繰り返し、考えうるすべてのテストにパスすれば終了です。

 こうやって作られたテストは大切に保存しておきましょう。このテストコードは提供された機能のすべてを実際に呼び出す、優れたサンプル(利用マニュアル)でもあるのです。後日機能の変更や拡張が必要となったとき、まずテストして元コードが正しく動作することを確認します。そして拡張/変更に基づきテストに追加/変更を加え、全テストをパスするようコードに手を加えます。

 常にテストされた状態を維持しながらコードの修正を行うことが重要なのです。そうすることであなたが書いたコードの信頼性/堅牢性を確保できるのですから。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/1075 2008/08/26 14:07

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング