Concolic Testingツール:CRESTによるテストケースの自動生成

ちょっと前に、C言語向けConcolic TestingツールであるCRESTを簡易的なサンプルで試したので、結果を簡単にまとめたいと思う。
なおCRESTを使用した雑感は以下の通り:

  • コードから実行パターンを生成するのみ。テストオラクル問題をどうにかしないといけない。
  • 制御パスの分析は大変高性能。複合条件も分析できるので、MC/DC網羅のテストケース生成や、パス数の見積もりが可能。
  • ループや再起など繰り返しの解析は行わない。例えばループ中で特定の関数を何回呼び出したかといったことは解析できない。ループカバレッジにも対応できず。
  • 外部コンポーネントの呼び出しでパス数が爆発しやすい。Test Doubleへの置換の仕組みが必要。

全体として、動的ユニットテストを用途に限ると、リグレッションテスト以外ではCRESTは使いにくい(テストオラクル問題を何とかしなければならない点と、コードベースである点などから)。そこを使えるようにするには独自のツールサポートやDbCの導入などが必要そう。ただ動的解析としては役に立つ用途がいくつかある。これについてはいずれ出せればと思う。

サンプルでの結果

単純な分岐

サンプル
int hoge(int a)
{
        CREST_int(a);

        if (a == 1) {
                return 1;
        }
        return 0;
}
解析結果
Read 2 branches.
Read 11 nodes.
Wrote 0 branch edges.
実行結果
Iteration 0 (0s): covered 0 branches [0 reach funs, 0 reach branches].
Iteration 1 (0s): covered 1 branches [1 reach funs, 2 reach branches].
Iteration 2 (0s): covered 2 branches [1 reach funs, 2 reach branches].
まとめ

単純な例でもあり、パスを網羅している。

switch

サンプル
int hoge(int a)
{
        CREST_int(a);

        switch (a) {
        case 0:
                return 1;
        case 1:
                return 2;
        default:
                return -1;
        }
        return 0;
}
解析結果
Read 6 branches.
Read 22 nodes.
Wrote 3 branch edges.
まとめ

ビルドして解析を行っているので、次のマクロ関数のサンプル含め網羅的にパスをピックアップできている。run_crestでもパスを全網羅している

マクロ関数

サンプル
#define FUGA(a) ((a==1) ? 1 : 0)

int hoge(int a)
{
        CREST_int(a);
        int b = FUGA(a);

        return (FUGA(b));
}
実行結果
Read 4 branches.
Read 13 nodes.
Wrote 4 branch edges.
まとめ

CRESTの仕様上、プリプロセッサを実行したあとのコードを網羅している。

ループ

サンプル
int hoge(int a)
{
        int i;
        CREST_int(a);
        for (i = 0; i < a; i++) {
                if (a < 100) {   
                        return 0;
                }
        }
        return 1;
}
解析結果
Read 2 branches.
Read 12 nodes.
Wrote 1 branch edges.
まとめ

基本的に分岐しか見ていない。分岐を扱うカバレッジについては高機能だが、ループカバレッジなどは扱えない。

再帰

サンプル
int hoge(int a)
{
        int b;
        CREST_int(a);
        if (a == 0) {
                return 1;
        }
        b = hoge(a - 1);
        return a * b;

}
解析結果
Read 4 branches.
Read 19 nodes.
Wrote 4 branch edges.
まとめ

ループと同様。再帰関数内にCREST_int()を記述したがこれは良くないかもしれない。なおこちらはrun_crestでのイテレーション数が20を超えた。

ランダム

サンプル
int hoge(int a)
{
        CREST_int(a);
        srand((unsigned int)time(NULL));

        if ((rand() % a) == 0)
        {
                return 0;
        }
        return 1;
}
解析結果
Read 2 branches.
Read 11 nodes.
Wrote 0 branch edges.
実行結果
Iteration 0 (0s): covered 0 branches [0 reach funs, 0 reach branches].
Floating point exception (core dumped)
Iteration 1 (0s): covered 1 branches [1 reach funs, 2 reach branches].
その他

当然かも知れないがrun_crestの実行結果は不安定。rand()のようなテスト対象外の外部コンポーネントはTest Double化が必要になる。

複合条件

サンプル
int hoge(int a)
{
        CREST_int(a);
        if (a > 200 && a < 400) {
                return 0;
        }
        return 1;
}
解析結果
Read 4 branches.
Read 13 nodes.
Wrote 2 branch edges.
まとめ

解析、実行ともに、複合条件を分解して網羅できていた。複合条件を扱えるユニットテストツールは高価な有償ツールが多いので、この点はCRESTが優秀だと感じる。