[ CCUnit project page ] [ CCUnit home page ]

CCUnit クックブック

これは CCUnit を使い始めるにあたって、 理解の助けとなるような短いクックブックです。

[see also English documents]

シンプルなテストケース

CCUnit を使ったテストは自動的に実行することができます。 CCUnit のテストは簡単にセットアップすることができ、 一度テストを書いてしまえば、 いつでもプログラムの品質を信頼できるものに保つことができるでしょう。

簡単なテストを作るには、次のようにします。

  1. テスト関数を作ります。
  2. 値をチェックしたい場合は、 CCUNIT_ASSERT(bool) を呼び出して、 テストが成功するなら真を返すような真偽値を渡します。

    ASSERT 関係のマクロは他にも ASSERT マクロにリストしてあります。

例えば、簡単な複素数のライブラリのテストを例にとってみましょう。 (ここで使用するサンプルプログラムexamples/complex ディレクトリにあります。) 複素数を初期化することができたかをテストしたり、 二つの複素数が等しいかをテストしたりします。

void test_complex_new ()
{
  complex_t* c10_1 = complex_new (10, 1);
  CCUNIT_ASSERT_TEST (double, 10, ==, c10_1->real);
  CCUNIT_ASSERT_TEST (double, 1, ==, c10_1->imaginary);
  complex_delete (c10_1);
}

void test_complex_equals ()
{
  complex_t* c10_1 = complex_new (10, 1);
  complex_t* c10_1_2 = complex_new (10, 1);
  complex_t* c1_1 = complex_new (1, 1);
  CCUNIT_ASSERT (complex_equals (c10_1, c10_1_2));
  CCUNIT_ASSERT (complex_equals (c10_1, c1_1) == 0);
  complex_delete (c10_1);
  complex_delete (c10_1_2);
  complex_delete (c1_1);
}

この二つのテスト関数をまとめてひとつのテストケースにして実行することができます。

必要なヘッダファイルをインクルードします。

#include <ccunit/CCUnitTestCase.h>
#include <ccunit/CCUnitTestResult.h>

まずテストケースにメモリを割り当てます。

extern void test_complex_new ();
extern void test_complex_equals ();

int main ()
{
  CCUnitTestCase* testCase;
  CCUnitTestResult* result;
  bool success;
  testCase = ccunit_newTestCase ("complex test");

そしてテストケースにテスト関数を登録します。 表明 (ASSERT) が失敗した時に表示するための関数名と関数の説明を一緒に指定しています。

  ccunit_addNewTestFunc (testCase,
                         "test_complex_new",
                         "test new",
                         test_complex_new);
  ccunit_addNewTestFunc (testCase, 
                         "test_complex_equals",
                         "complex equals test",
                         test_complex_equals);

テストケースを実行し、 結果を取り出します。

  result = ccunit_runTestCase (testCase);
  success = ccunit_wasSuccessful (result);

割り当てたメモリを解放し、 結果を返します。

  ccunit_deleteTestCase (testCase);
  ccunit_deleteTestResult (result);
  return success ? 0 : -1;
}

ここまでのサンプルコードは、examples/complex ディレクトリの testComplex.c, runTestCase.c にあります。

コンパイルおよび実行するには、次のようにします。

$ gcc -o runTestCase runTestCase.c testComplex.c complex.c -lccunit
$ ./runTestCase
$ echo $?
0

テストケースが実行されると、 指定したテスト関数が呼び出されます。 これはまだあまり便利ではありません、 なぜなら、診断が表示されないからです。 通常はテストの実行で結果を表示します。

テストランナー

テストを実行したときに、どうやってその結果を集めたら良いでしょうか。 テストケースはテスト関数の実行結果 TestFailure TestResult に格納します。 同時にテストの実行状況を TestRunner に通知します。

inline_dotgraph_1.dot

一つテストケースを書いたら、 それを実行したいと思うでしょう。 CCUnit の TestRunner は、 テストケースをテストスイートにまとめて実行し結果を表示します。

TestRunner を使用するには、 例えば runTestCaseRunner.c でテストのためのファイルのヘッダをインクルードします。

#include <ccunit/CCUnitTestCase.h>
#include <ccunit/CCUnitTestSuite.h>
#include <ccunit/CCUnitTestRunner.h>

まず TestSuite を作成し、 それに TestCase を登録します。

extern void test_complex_new ();
extern void test_complex_equals ();

int main ()
{
  CCUnitTestCase* testCase;
  CCUnitTestSuite* suite;
  CCUnitTestRunner* runner;
  int ret;
  testCase = ccunit_newTestCase ("complex test");
  ccunit_addNewTestFunc (testCase,
                         "test_complex_new",
                         "test new",
                         test_complex_new);
  ccunit_addNewTestFunc (testCase, 
                         "test_complex_equals",
                         "complex equals test",
                         test_complex_equals);
  suite = ccunit_newTestSuite ("complex test suite");
  ccunit_addTestCase (suite, testCase);

そして ccunit_runTestRunner(CCUnitTestRunner*, CCUnitTestSuite *) を実行します。

  runner = ccunit_newTestRunner (stdout);
  ret = ccunit_runTestRunner (runner, suite);

割り当てたメモリを解放します。 ただし、testCasesuite に登録してあるので、 suite を解放する時に一緒に解放されます。 そのため testCase はここで解放する必要がありません。 解放しようとすると Bus error などになりますので注意してください。

  ccunit_deleteTestRunner (runner);
  ccunit_deleteTestSuite (suite);
  /* ccunit_deleteTestCase (testCase); // testCase deleted when suite was deleted */
  return ret;
}

ここまでのサンプルコードは、examples/complex ディレクトリの runTestCaseRunner.c にあります。

コンパイルおよび実行するには、次のようにします。

$ gcc -o runTestCaseRunner runTestCaseRunner.c testComplex.c complex.c -lccunit
$ ./runTestCaseRunner
..
Time: 0.000027 sec

OK (2 tests)

TestRunner はテストを実行します。 もしすべてのテストがパスすれば、その情報のメッセージが表示されます。 上の例では、最初の「..」は実行したテストケースです。 実行する直前に「.」が出力されます。 全てのテストケースの実行が終了すると、 実行にかかった時間と実行したテストケースの数が表示されます。

どれかが失敗すれば、それについて以下のような情報が表示されます。

試しに testComplex.c のテストケースでわざと間違えてみましょう。

void test_complex_equals ()
{
  ...
  CCUNIT_ASSERT (complex_equals (c10_1, c10_1_2));
/*CCUNIT_ASSERT (complex_equals (c10_1, c1_1) == 0);*/
  CCUNIT_ASSERT (complex_equals (c10_1, c1_1) != 0);
  ...
}
$ gcc -o runTestCaseRunner runTestCaseRunner.c testComplex.c complex.c -lccunit
$ ./runTestRunner
..F
Time: 0.000036 sec

FAILURES!!!
Test Results: 
Run 2, Failures 1
There was 1 failure:
testComplex.c:46: complex equals test:
        complex_equals (c10_1, c1_1) != 0

この例ではテストケースを 2 個実行して、 ひとつのテストケースで失敗していることがわかります。 最初の「..F」では、 ふたつ目のテストケースが失敗していることをあらわしています。

また上述のように、 失敗したファイル名、行番号、テストケースの名前 (complex equals test)、 表明した条件の文字列 (complex_equals (c10_1, c1_1) != 0) が表示されます。

これは大変単純なテストです。 通常、 たくさんの小さなテスト関数を作って、 同じようなデータの集まりを使ってテストを行うでしょう。 つまり各テスト関数共通のグローバル変数を使うことになります。

setUp と tearDown

もし複数のテスト関数があるなら、 同じデータの集まりをいくつか使うのではないでしょうか。

inline_dotgraph_2.dot

テストは中身のわかっているデータの集まりを前提として実行される必要があります。 テストを書いていると、 実際にテストするコードよりも、 値をセットアップするコードを書く方に、 より時間をかけていることがよくあります。

例えば前節のサンプルコードでは、 いくつかの複素数を割り当てたり解放したりしています。

多くの場合、いくつかの異なったテストのために同じグローバル変数を使うことができます。 それぞれのテスト関数を実行する前に変数を初期化し、 値を調べることになるでしょう。 そしてテストが終わったら後始末することになります。

setUptearDown 関数はそんな時に使用することができます。

inline_dotgraph_3.dot

もし各テストに共通のグローバル変数があれば、 次のようにすることでしょう。

グローバル変数を定義します。

#include "complex.h"

static complex_t* c10_1;

複素数を割り当てる setUp 関数を書きます。

void setUp_test_complex ()
{
  c10_1 = complex_new (10, 1);
}

割り当てたメモリを解放する tearDown 関数を書きます。

void tearDown_test_complex ()
{
  complex_delete (c10_1);
}

テスト関数を書きます。

void test_complex_new ()
{
  CCUNIT_ASSERT_TEST (double, 10, ==, c10_1->real);
  CCUNIT_ASSERT_TEST (double, 1, ==, c10_1->imaginary);
}

void test_complex_equals ()
{
  complex_t* c10_1_2 = complex_new (10, 1);
  complex_t* c1_1 = complex_new (1, 1);
  CCUNIT_ASSERT_TEST_OBJ (c10_1, complex_equals, c10_1_2, complex_to_string);
  CCUNIT_ASSERT_TEST_OBJ (c10_1, !complex_equals, c1_1, complex_to_string);
  complex_delete (c10_1_2);
  complex_delete (c1_1);
}

setUptearDown を登録します。 この時、関数の名前が setUp/tearDown で始まっていなければなりません。

int main ()
{
  CCUnitTestCase* testCase;
  CCUnitTestSuite* suite;
  CCUnitTestRunner* runner;
  int ret;
  testCase = ccunit_newTestCase ("complex test");
  ccunit_addNewTestFunc (testCase,
                         "setUp_test_complex",
                         "setUp_test_complex",
                         setUp_test_complex);
  ccunit_addNewTestFunc (testCase, 
                         "tearDown_test_complex",
                         "tearDown_test_complex",
                         tearDown_test_complex);

テストケースを実行し、 結果を返します。

  ccunit_addNewTestFunc (testCase,
                         "test_complex_new",
                         "test new",
                         test_complex_new);
  ccunit_addNewTestFunc (testCase, 
                         "test_complex_equals",
                         "complex equals test",
                         test_complex_equals);
  suite = ccunit_newTestSuite ("complex test suite");
  ccunit_addTestCase (suite, testCase);
  runner = ccunit_newTestRunner (stdout);
  ret = ccunit_runTestRunner (runner, suite);
  ccunit_deleteTestRunner (runner);
  ccunit_deleteTestSuite (suite);
  return ret;
}

ここまでのサンプルコードは、examples/complex ディレクトリの testComplexSetup.c, runTestCaseSetup.c にあります。

コンパイルおよび実行するには、次のようにします。

$ gcc -o runTestCaseSetup runTestCaseSetup.c testComplexSetup.c complex.c -lccunit
$ ./runTestCaseSetup
..
Time: 0.000048 sec

OK (2 tests)

一回だけの setUp/tearDown

前の節ではテストごとに毎回初期化を行っていました。 しかし、 一回だけ初期化を行って、 複数のテストを実行したい場合があります。 そしてテストが全て終わったときだけ後始末のコードを実行するような場合があります。

そのためにはテストケースに setup_setUp, setup_tearDown 関数を登録します。 すると次のような順序でテスト関数が実行されます。

inline_dotgraph_4.dot

スーツ

複数のテストを全部一度に実行することができるように準備するには、 どのようにしたらいいでしょうか? いくつかのテストを作ったら、 それらをスーツ (スイート) に整理します。

inline_dotgraph_5.dot

CCUnit はいくつもの TestFuncs を一緒に実行する TestSuite モジュールを提供します。 テストスイートを実行する方法はすでに前の方で示してあります。

二つ以上のテストを含む一つのスーツを作るには、次のようにします。

テストケースを二つ生成し、それぞれにテスト関数を登録します。

int main ()
{
  CCUnitTestCase* testCase, * testArith;
  CCUnitTestSuite* suite;
  CCUnitTestRunner* runner;
  int ret;
  /* test complex */
  testCase = ccunit_newTestCase ("test complex");
  ccunit_addNewTestFunc (testCase,
                         "setUp_test_complex",
                         "setUp_test_complex",
                         setUp_test_complex);
  ccunit_addNewTestFunc (testCase, 
                         "tearDown_test_complex",
                         "tearDown_test_complex",
                         tearDown_test_complex);
  ccunit_addNewTestFunc (testCase,
                         "test_complex_new",
                         "test new",
                         test_complex_new);
  ccunit_addNewTestFunc (testCase, 
                         "test_complex_equals",
                         "complex equals test",
                         test_complex_equals);
  /* test complex arith */
  testArith = ccunit_newTestCase ("test complex arith");
  ccunit_addNewTestFunc (testArith,
                         "setUp_test_complex_arith",
                         "setUp_test_complex_arith",
                         setUp_test_complex_arith);
  ccunit_addNewTestFunc (testArith, 
                         "tearDown_test_complex_arith",
                         "tearDown_test_complex_arith",
                         tearDown_test_complex_arith);
  ccunit_addNewTestFunc (testArith,
                         "test_complex_add",
                         "test add",
                         test_complex_add);
  ccunit_addNewTestFunc (testArith, 
                         "test_complex_sub",
                         "test sub",
                         test_complex_sub);
  ccunit_addNewTestFunc (testArith,
                         "test_complex_mul",
                         "test mul",
                         test_complex_mul);
  ccunit_addNewTestFunc (testArith, 
                         "test_complex_div",
                         "test div",
                         test_complex_div);

テストスイートを生成し、 テストケースを登録します。

  suite = ccunit_newTestSuite ("complex test suite");
  ccunit_addTestCase (suite, testCase);
  ccunit_addTestCase (suite, testArith);

テストランナーを生成しテストを実行します。

  runner = ccunit_newTestRunner (stdout);
  ret = ccunit_runTestRunner (runner, suite);
  ccunit_deleteTestRunner (runner);
  ccunit_deleteTestSuite (suite);
  return ret;
}

ここまでのサンプルコードは、examples/complex ディレクトリの runTestSuite.c, testComplexSetup.c, testComplexArith.c にあります。

コンパイルおよび実行するには、次のようにします。

$ gcc -o runTestSuite runTestSuite.c testComplexSetup.c testComplexArith.c complex.c -lccunit
$ ./runTestSuite
......
Time: 0.000061 sec

OK (6 tests)

TestSuites TestCases を含むだけではありません。 TestSuites 自身を含むこともできます。 例えば、あなたはあなたのコードに TestSuite を作ることができ、 そして私は私のスーツ を作ることができます、 そして私達は両方ともを含んでいる TestSuite を作って、 一緒にそれぞれのスーツを実行することができるのです。

  CCUnitTestSuite* suite;
  CCUnitTestSuite* suite_a;
  CCUnitTestSuite* suite_b;

  ...

  suite = ccunit_newTestSuite ("complex all test suite");
  suite_a = ccunit_newTestSuite ("complex test suite A");
  suite_b = ccunit_newTestSuite ("complex test suite B");

  ...

  ccunit_addTestSuite (suite, suite_a);
  ccunit_addTestSuite (suite, suite_b);

ヘルパーツール

お気づきのように、 テストケースにテスト関数を登録したり、 テストスイートにテストケースを登録したりする作業は、 反復的で間違いやすいものです。

例えば testComplexSetup.c には test_complex_to_string() というテスト関数が定義されていますが、 今までの説明では無視されていてテストケースには登録されませんでした。

このような登録漏れをなくすために テストスイートの生成 の関数とコマンドは、 テストケースやテストスイートを登録する関数の実装を、 自動的に作成することができます。

そのためには、テストケースを書くファイルを次のように作ります。

最初に、テストスイートを宣言します。 これは javaDoc スタイルのコメント内にテストスイートの名前を記述します。 javaDocスタイルのコメントとは C スタイルのコメントブロックの開始が 二つのアスタリスク ** になっているものです。

/** test suite: complex test suite */

次に、 テストケースを定義します。 テストケースは javaDoc スタイルのコメントに書かれたテストケースの名前の宣言と、 テストケースの終了の宣言で囲まれたグローバル変数と関数の集まりです。 ひとつのファイルに複数のテストケースを定義できます。

/** test case: complex test case */

static complex_t* c10_1;

/** test new */
void test_complex_new ()
{
  ...
}

...

/** end test case */
/** test case: complex arith test case */
...
/* end test case */
/* end test suite */
/* test suite: ... */
...
/* end test suite */

テスト関数は名前が test で始まっていなければなりません。 また各テスト関数の前には javaDoc スタイルのコメントの中にテスト関数の説明を書くことができます。 コメントが書かれていなければ、 テスト関数の名前が関数の説明として登録されます。

特別なテスト関数として setUp, tearDown, setup_setUp, setup_tearDown があります。 その名前からわかるように、 以前に説明したテストケースの機能の関数として働きます。 それぞれそのような名前で定義してください。

スーツ作成関数のコードを生成するには、 ccunit_makeSuiteツールを実行します。

自動的に作成される TestSuite を作成する関数は、 デフォルトでは ccunit_suite です、 しかし ccunit_makeSuite コマンドの -f オプションで別の名前に変えることもできます。

$ ccunit_makeSuite -f suite_test_complex -o suiteTestComplex.c testComplexSetup.c testComplexArith.c

自動生成したコードを利用するには次のようにします。

生成した関数のプロトタイプを宣言します。 引数の char* name を忘れないようにしてください。

#include <stdio.h>
#include <ccunit/CCUnitTestSuite.h>
#include <ccunit/CCUnitTestRunner.h>

extern CCUnitTestSuite* suite_test_complex (char* name);

生成した関数を呼び出してテストスイートを取り出します。

int main ()
{
  CCUnitTestSuite* suite;
  CCUnitTestRunner* runner;
  int ret;
  suite = suite_test_complex ("complex suite auto generated");

テストランナーで実行します。

  runner = ccunit_newTestRunner (stdout);
  ret = ccunit_runTestRunner (runner, suite);
  ccunit_deleteTestRunner (runner);
  ccunit_deleteTestSuite (suite);
  return ret;
}

ここまでのサンプルコードは、examples/complex ディレクトリの runTestSuiteAuto.c, testComplexSetup.c にあります。

コンパイルおよび実行するには、次のようにします。

$ ccunit_makeSuite -f suite_test_complex -o suiteTestComplex.c testComplexSetup.c testComplexArith.c
$ gcc -o runTestSuiteAuto runTestSuiteAuto.c testComplexSetup.c testComplexArith.c suiteTestComplex.c complex.c -lccunit
$ ./runTestSuiteAuto
.......
Time: 0.000060 sec

OK (7 tests)

テストケースソースコードは次の疑似 BNF によってフォーマットされなくてはなりません。

 SOURCEFILE ::= [SUITE]... | TESTCASE...

 SUITE ::= SUITE_HEADER
           [ any-C-code ]...
           TESTCASE...
           SUITE_END

 SUITE_HEADER ::= JAVADOC_COMMENT_BEGIN 'test suite:' SUITE_NAME COMMENT_END
 SUITE_END ::= JAVADOC_COMMENT_BEGIN 'end test suite' wsp string COMMENT_END

 TESTCASE ::= TESTCASE_HEADER
             [TESTCASE_CODE]...
             [TESTFUNC]...
             TESTCASE_END

 TESTCASE_HEADER ::= JAVADOC_COMMENT_BEGIN
                    'test case:' TESTCASE_NAME
                    COMMENT_END
 TESTCASE_CODE ::= any C language codes.

 TESTFUNC ::= [ FUNC_DESC ] 'void ' FUNC_PREFIX[A-Za-z0-9_]* '()' FUNC_BODY

 FUNC_PREFIX ::= 'test' | 'setUp' | 'tearDown' | 'setup_setUp' | 'setup_tearDown'

 TESTCASE_END ::= JAVADOC_COMMENT_BEGIN 'end test case ' [string] COMMENT_END

 FUNC_DESC ::= JAVADOC_COMMENT_BEGIN string COMMENT_END

 FUNC_BODY ::= '{' C language codes... '}'

 JAVADOC_COMMENT_BEGIN ::= '/' '*' '*'
 COMMENT_END ::= '*' '/'

サンプルプログラム

以上で作成したサンプルプログラムは examples/complex ディレクトリにあります。

SourceForge.jp hosts this site. Send comments to: CCUnit Developer