レガシーコード改善勉強会にて登壇

少し前の話ですが、レガシーコード改善勉強会というイベントで登壇させていただきました。
内容としては、自分の組み込みと少し離れた場ということもあり、普遍的なレガシーコードとの扱い方について話させて頂きました。

当日は他の講演もバリエーション豊かで、学ぶものの多い有意義な時間となりました。
登壇を誘っていただいた有地さん、運営者、登壇者、参加者の方々、改めてありがとうございました。

システムテスト自動化標準ガイドを翻訳

発売は年末となりますが、システムテスト自動化標準ガイド(原題Software Test Automation)という書籍を、有志の仲間で翻訳させていただきました。


Amazon.co.jp: システムテスト自動化 標準ガイド (CodeZine BOOKS): Mark Fewster, Dorothy Graham, テスト自動化研究会: 本

自分は主にメトリクスの章が担当です。
少し古い本ですが、プロセスや管理について有益な内容がよくまとまった本だと思っています。
手にとっていただけると幸いです。

ソフトウェア品質シンポジウムにてConcolic TestingのSIGを開催

前ブログに続きますがソフトウェア品質シンポジウムにて、Concolic TestingのSIGのサブリーダーを担当させていただきました。

SIGでは色々と技術的な課題が指摘されましたが、研究会としてそれらの対応策を検討していければと考えています。
参加者の方々、大変有難うございました。ソフトウェア品質シンポジウムにてConcolic TestingのSIGを開催

ソフトウェア品質シンポジウム パネルディスカッションに登壇

先日、ソフトウェア品質シンポジウムにて、「レビューとテストは使い分けるべきか?」のパネルディスカッションにパネラーとして参加させていただきました。
http://www.juse.jp/sqip/symposium/detail/day2/#session_d4

ディスカッション中に用いた資料


レポートは後ほど出てくるかと思います。
セッション中の議論も楽しく、有意義な時間となりました。
誘っていただいた細谷さんを始め、参加者の方々、パネリストの方々、大変有難うございました。

WACATE2014夏にてテスト設計のワークショップ実施

少し前の話になりますが、先日WACATE2014夏というソフトウェアテストの合宿勉強会にて、テストに関わるワークショップを担当させていただきました。

ワークショップ解説資料

プログラム概要


テーマは、テスト設計を対象とした、リバースエンジニアリング、追加変更、保守性改善の3つで構成しています。具体的には、問題を抱えるテスト手順書と仕様書を用いて、以下を実施頂きました。

  • テスト設計のリバースエンジニアリングを行い、リバースしたテスト設計に追加変更を加えて再度手順書に展開する。
  • テストの保守性の問題を分析し、その改善策を提案して頂く。


(WACATEではよくありますが)ワークショップでは参加者の方々に想定外の優れたアプローチを構築頂き、モデレータ側としても、学ぶ所が多い有意義な場となりました。

解説資料以外の資料についてはまだオープンにしていませんが、機会があれば公開できればと考えています。
実行委員・参加者の方々には改めてお礼申し上げます。

CRESTの生成値が記録されない問題

CRESTは、デフォルトでは解析対象を実行する際に生成した値を出力しない。
生成値を出力する方法としては、公式サイトにて、run_crest本体のコードに細工する方法が以下で説明されている。
https://github.com/jburnim/crest/wiki/CREST-Frequently-Asked-Questions#does-crest-save-the-test-inputs-it-generates

ただ上記の場合、一回のイテレーションで生成する値がすべて0だった場合、値が保存されないようになっている。0とそれ以外での挙動の差異は、CRESTを使ってテスト設計からテスト実行まで自動化しようとする際に注意がいる。

原因として、コードを見ると、解析処理を行うSearch::RunProgram()にて、生成値を格納した引数inputsのサイズが0だと、生成する値をすべて0に初期化する処理を行っているようだ。そして調べて見る限り、CRESTは解析ですべての生成値が0だった場合、inputsをサイズ0のままにしてSearch::RunProgram()を実行し、生成値を初期化している。


そのため対策だけど、「記録された生成値ファイルが空であれば、生成値はすべて0にする」と判断すれば一応支障はなさそうだ。
一方0以外の時と同じように値を記録する際は、ソースコードでのSearch::WriteInputToFileOrDie()に細工が必要になる。例えば生成対象の変数が1つだけなら、以下の様な細工をすると生成値が0かどうかで場合分けが不要になる。

void Search::WriteInputToFileOrDie(const string& file, const vector<value_t>& input) {
  FILE* f = fopen(file.c_str(), "w");
  if (!f) {
    fprintf(stderr, "Failed to open %s.\n", file.c_str());
    perror("Error: ");
    exit(-1);
  }

  for (size_t i = 0; i < input.size(); i++) {
    fprintf(f, "%lld\n", input[i]);
  }
  if (input.size() == 0) {
    fprintf(f, "0\n");
  }
  fclose(f);
}

ただ、そもそも上記の公式の方法では、生成値の型や変数名といった構文情報が失われる。生成値を記録する際は、マクロのCREST_*()に細工して、解析対象から直接生成値を出力させたほうが都合が良いかもしれない。

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が優秀だと感じる。