C言語・C++言語用単体テスト・ユニットテストフレームワーク - Cutter

機能

機能 — Cutterの機能

はじめに

Cutterは以下のような単体テストフレームワークの基本的な機能を 持っています。

  • フィクスチャ

  • テスト登録コード不要

  • デバッグに便利な結果出力

  • 豊富な検証機能

Cutterは以下のようなテスト環境をもっと便利にする高度な機能も あります。

  • 複数のプラットフォーム対応

  • データ駆動テストのサポート

  • カバレッジのサポート

  • クラッシュ時のバックトレース出力

  • テスト結果の保存・復元

  • マルチプロセス・マルチスレッドのサポート

  • 画像差分

  • ...

基本機能

単体テストフレームワークが一般的に提供している機能について、 Cutterがどのようにその機能を提供しているかについてを説明しま す。

フィクスチャ

単体テストフレームワークでいうフィクスチャとは、各テストを実 行する前にテスト用データを用意するための仕組みのことです。こ れは、一般的には各テスト毎にsetup/teardownと呼ばれる初期化処 理/終了処理を実行することによって実現します。

Cutterでは以下のようにテストプログラム中に cut_setup()/cut_teardown() 関数を定義すると、それらの関数が初 期化処理/終了処理として扱われます。

void
cut_setup (void)
{
   /* 初期化処理 */
}

void
cut_teardown (void)
{
   /* 終了処理 */
}

また、Cutterではテストケース毎の初期化処理/終了処理のために cut_startup()/cut_shutdown()もサポートしてます。

void
cut_startup (void)
{
   /* テストケースの初期化処理 */
}

void
cut_shutdown (void)
{
   /* テストケースの終了処理 */
}

これらの関数は以下のような順番で呼ばれます。

  • cut_startup()

    • cut_setup()

      • テスト1実行

    • cut_teardown()

    • cut_setup()

      • テスト2実行

    • cut_teardown()

    • ...

  • cut_shutdown()

また、実験的な機能ですが、テスト全体を実行する前、テスト全体 を実行した後に呼び出す関数を登録することもできます。これらの 関数をそれぞれwarmup/cooldownと呼んでいます。呼び出し順序は こうなります。

  • warmup実行

    • テストケース1のcut_startup()

      • テストケース1のcut_setup()

        • テスト1-1実行

      • テストケース1のcut_teardown()

      • テストケース1のcut_setup()

        • テスト1-2実行

      • テストケース1のcut_teardown()

      • ...

    • テストケース1のcut_shutdown()

    • テストケース2のcut_startup()

      • テストケース2のcut_setup()

        • テスト2-1実行

      • テストケース2のcut_teardown()

      • テストケース2のcut_setup()

        • テスト2-2実行

      • テストケース2のcut_teardown()

      • ...

    • テストケース2のcut_shutdown()

    • ...

  • cooldown実行

この機能は、テスト対象のライブラリがライブラリ初期化関数・終 了関数を用意している場合に有用です。ただ、この機能は実験的な 機能なのでここでその使い方を紹介するのは控えておきます。もし、 使いたい場合は聞いてください。


テスト登録コード不要

動的な言語用の単体テストフレームワークの多くでは明示的にテス トを登録する必要はありません。自動的にテストメソッド・テスト 関数などを見つけて実行します。しかし、C言語用の単体テストフレー ムワークの多くでは明示的にテストを登録する必要があります。

Cutterはテストを簡単に書けるようにするため、多くの動的な言語 用の単体テストフレームワークのように自動的にテスト関数を見つ けます。そのため、以下のように名前が「test_」からはじまる公開 関数を定義するだけでその関数がテスト関数として認識されます。

void test_my_function (void);

void
test_my_function (void)
{
    /* テスト関数 */
}

デバッグに便利な結果出力

Cuterは迅速に問題の確認・修正が行えるようにテスト結果を出力し ます。具体的には以下のように出力を行います。

  • 問題がない部分はシンプルに

  • 問題がある部分は冗長に

まず、問題がない部分をシンプルに表示する(時には何も表示しな い)ことにより大事な情報が埋もれてしまうことを防ぎます。

また、問題がある部分はどのような問題があるかを判断するために、 知っている情報をできるだけ多く表示します。

例えば、文字列が同じ内容かを比較するテストで文字列が異なって 場合を考えます。Cutterは期待値と実測値を並べて表示します。こ れによりどの部分が異なるかを目視で確認しやすくなります。

expected: <abc def ghi jkl>
 but was: <abc DEF ghi jkl>

もし、これがずれて表示されていたり、同じ行に表示されていると どこが異なるかを見つけるのは大変になります。

expected: <abc def ghi jkl>
but was: <abc DEF ghi jkl>

<abc def ghi jkl> is expected but was <abc DEF ghi jkl>

また、必要ならば期待値と実測値のdiffを表示して具体的にどこが 異なるのかも示します。

expected: <abc def ghi jkl>
 but was: <abc DEF ghi jkl>

diff:
- abc def ghi jkl
?     ^^^
+ abc DEF ghi jkl
?     ^^^

このように、Cutterにはテストが失敗した時に迅速に問題を確認す るための工夫が施されています。これにより、開発者が迅速に問題 を修正することを支援します。


豊富な検証機能

xUnit系の単体テストフレームワークではテスト対象が期待する動 作をしているかを検証するために、assertionと呼ばれる検証機能 を提供します。例えば、一般的には以下のような検証機能がありま す。

  • assert: 検証対象が真の値であることを検証

  • assert_equal: 実測値が期待値と等しいことを検証

Cutterでは、以下の検証機能が上記の検証機能に対応します。

  • cut_assert()

  • cut_assert_true(): 機能はcut_assert()と同じだが「真の値」 であることを明示(自己記述的なためこちらの利用を推奨)

  • cut_assert_equal_int()

  • cut_assert_equal_uint()

  • cut_assert_equal_string()

  • ...

Cutterは上記のような一般的な検証機能以外にも様々な検証機能が 組み込みで提供しているので、より簡単にテストを書くことができ ます。例えば、以下のような検証機能を提供しています。

  • cut_assert_errno(): errnoが0であることを検証

  • cut_assert_match(): 実測値の文字列が指定した正規表現にマッ チすることを検証

  • cut_assert_path_exist(): 指定したパスが存在することを検 証

  • ...

検証機能の一覧はリファレンスマニュアルの 検証 や GLibサポート付きの検証 を見てください。

高度な機能

一部の単体テストフレームワークが提供している機能のCutterでの 提供の仕方、および、どの単体テストフレームワークも提供してい ないCutter独特の機能について提供について説明します。

複数のプラットフォーム対応

現在、以下のプラットフォームでの動作を確認しています。

  • GNU/Linux

  • FreeBSD

  • Mac OS X

  • Windows (MinGW)


データ駆動テストのサポート

複数のデータに対して同じテストを実行する場合があります。例え ば、以下のような場合が考えられます。

  • 複数の入力パターンがあり、それらを網羅的にテストする場合

  • 複数のバックエンドを抽象化し、どのバックエンドを利用して いる場合でも同じインターフェイスで扱えるライブラリをテス トする場合。(cairoやDBIなど)

このような場合、テスト自体は1つだけ定義し、各テストデータに 対してそのテストを実行することで効率的にテストを作成すること ができます。このようなテストの仕方はデータ駆動テストと呼ばれ ています。

Cutterでのデータ駆動テストの書き方については cut_add_data() を見てください。

データ駆動テストの場合はテストは以下のような流れで実行されま す。

  • テストデータ生成関数呼び出し

  • cut_setup()

    • テストデータ1を使ってテスト実行

  • cut_teardown()

  • cut_setup()

    • テストデータ2を使ってテスト実行

  • cut_teardown()

  • ...


カバレッジのサポート

カバレッジ率はどの程度テストを網羅的に行っているかを示す指標 になります。

CutterはGCCを使用したカバレッジ測定を支援するためのM4マクロを 提供しています。GNU Autoconf/GNU Automakeを利用している場合は このM4マクロを利用することにより、カバレッジ測定環境を簡単に ビルドシステムに組み込むことができます。

詳しくは README.jaチュートリアル の AC_CHECK_COVERAGEについて書かれている部分を見てください。


クラッシュ時のバックトレース出力

C言語・C++言語で実装されたプログラムではSegmentation Faultで プログラムが異常終了することは珍しくありません。この時、 CutterはSEGV シグナルが発生した時点でのバックトレースの取得を 試みます。取得できた場合はバックトレースを出力してから終了し ます。もちろん、この時点でテストプロセスがなにかしら破壊され ているので、必ずしもバックトレースを取得できるわけではありま せん。

問題の詳細を調べるには、GDBなどのデバッガで処理を追いかけて いく必要がありますが、バックトレースをデバッグの最初の足がか りとして利用することができます。


テスト結果の保存・復元

ソフトウェアの品質を確認する方法として以下のような方法があり ます。

  • テスト状況とバグ発見数の推移を測定

  • テスト状況とバグ報告数の推移を測定

  • テスト状況とソース規模の推移を測定

例えば、テストが増えているのにバグ発見数が少ない場合は、効率 の悪いテストを行っている、あるいはもともとテスト対象の品質が 高かったということが考えられます。テストが増えているのにバグ 報告数が伸びている場合は的外れなテストを行っているかもしれま せん。ソース規模が大きくなっているのにテストが増えていない場 合はテスト不足が考えられます。

このように、その時点でのテスト状況だけではなく、過去のテスト 結果も利用して時系列でソフトウェアの開発状況を分析することに より、ソフトウェアの品質向上に役立てることができる場合があり ます。

Cutterはテスト結果をXMLとしてファイルに保存することができます。 また、保存したXMLを読み込んでテスト結果を復元することができま す。

まだ実装されていませんが、保存したテスト結果を読み込んで、時 系列のグラフとしてレポートを出力する機能の実装を予定していま す。


マルチプロセス・マルチスレッドのサポート