機能

機能 — 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チュートリアル のAC_CHECK_COVERAGEについて書かれている部分を見てください。


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

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

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


テスト結果の保存・復元

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

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

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

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

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

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

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

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


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