ソースコードレビューと単体テストの違い

我々の犯す一つの大きな間違いは、原因を結果の間近にあると考えることにある。
ゲーテ

ソフトウェア開発において、できるだけ上流で欠陥を検出し、不具合を下流に流出させないことは、成果物の品質確保と開発全体の工数削減のために非常に重要になります。そのための代表的な手段が、「ソースコードレビュー」と「単体テスト(ユニットテスト)」です。
この二つ、どちらも同じようなもので、どちらか一方で、もう片方を代替できると思われることもありますが、実際に大きな違いがあります。今回は、その違いについて書きたいと思います。

ソースコードレビューと単体テストの違い

ソフトウェア開発のモデルでV字モデルと言われるものがあります。要求分析から詳細設計・コーディングまでの上流工程に対して、それぞれの工程に対応するテスト工程によって、その成果物が検証されるようなモデルです。

V字モデル

単体テストは、いわゆる詳細設計に対応するテスト工程になっていますが、ソースコードレビューはどこにマッピングされるものでしょうか。コーディングの一環と見方もできますが、単体テストと同じく、実装完了したソースコードを対象とするものですので、単体テストと同じく、詳細設計に対応する検証工程と考えるのが普通かと思います。

では、単体テストとソースコードレビューはどちらか片方だけすればいいものでしょうか。以下で紹介されている内容がわかりやすかったのでご紹介したいと思います。 ここに書かれている内容の要点だけまとめると、以下のような形になるかと思います。
  • ソースコードレビューも単体テストもINPUTは同じ。どちらも関数レベルのソースコード。
  • 検出率としてはソースコードレビューの方が優秀。
  • ソースコードレビューは、レビュアーのスキルレベルに依存し、見落としもあり得る
  • ソースコードレビューが品質向上工程で、テストの方は品質保証工程
品質向上と品質保証という言い方は、わかりやすいですね。

結局、両方必要ということなのですが、これらの違いはもう少し理解して、使い分けないと効果的な検証にはなりません。この違いをもう少し考えてみます。

テストは結果、レビューはプロセスを検証する

単体テストは、入力に対し、期待する結果がでるかを確認します。単体テストの指標として命令網羅(C0)や分岐網羅(C1)といったカバレッジ指標を用いることがありますが、これはあくまでテストの網羅率を計測しているものであって、評価そのものではありません。あくまで、検証対象は、INPUTに対するOUTPUTのみです。つまり、単体テストで保証しているのは、テストした範囲での入出力が仕様を満たしているということだけであり、設計の中身については何も保証してくれません。

では、ソースコードレビューの方はどうでしょうか。こちらは、入出力の対応を見るというよりは、関数の実装内容、ソースコードの中身を確認するのが中心になります。つまり、単体テストとは逆で、中身(経過)が正しいことを確認して、そこから結果が正しくなるはずということを検証するアプローチです。言い換えると、結果ではなく中身を検証するアプローチです。もう少しいい方を変えると、設計内容を検証するというものです。

そう考えると、単体テストはホワイトボックステストに分類されるかもしれませんが、関数レベルではブラックボックステストです。結果しか見ていない以上、単体テストだけで品質を保証するには、関数内が少しでも変われば、基本的には全部をテストしないと正しいということは言えません。ですので、効率的な単体テストで重要なのは、その再現性、すなわち、テスト実施者への依存性がなく、コード修正のたびに検証を効率的に実施できる仕組みになります。

一方、ソースコードレビューの目的は、その中身、つまり設計を検証することにあります。ソースコードレビューが、単なる機能レビューみたいになっているケースがありますが、それだけではソースコードレビューを実施する目的からすると不十分です。 ソフトウェアの実装は、たとえ関数レベルであっても、その作りによって、実行効率や拡張性、可読性は大幅に変わってきます。こういった、単なる結果が正しいかというだけではない、設計観点での検証がソースコードレビューでは重要になってきます。
よって、より良いレビューのためには、レビュアーの選定が重要になります。システムやサービス、商品の仕様に精通しているというレビュアーだけではなく、ソフトウェア設計の観点で適切なレビューができるレビュアーを入れることが重要です。そうでないと、ソースコードレビューが、機能レビュー、仕様レビューになってしまいます。

静的解析で単体テストとソースコードレビューの穴を埋める

詳細設計、コーディングプロセスに対する検証方法として、単体テスト、ソースコードレビューと並んで有用な方法が、静的解析です。有名どころでは、PG-ReliefやCoverity Quality Adviser等のツールを活用した静的なソースコード検証です。大規模なソフト開発になると、網羅的なソースコードレビューは非常に困難になりますが、静的解析のような機械的なチェックはその補完として非常に強力なツールになります。なお、一口に静的解析パターンマッチ型やビルドキャプチャー型等の種類があり、それぞれ得意とするスコープが違いますので、目的とするプロジェクトにあったものを選ぶことが必要です。わかりやすい説明があったので、リンクを貼っておきます。
今回は、ソースコードレビューと単体テストの違いをテーマに少し書いてみました。ソフトウェア開発のプロセスでは、様々な手順やアウトプット等がルール化されていると思いますが、単にルールだからといって実施するのではなく、っそのメリットや目的を正しく理解することが、効率的な開発及び品質向上のためには重要かと思います。

4817192631 【関連記事】
【関連書籍】

[C言語] 配列の添字に負の値は使えるか

今は終わりではない。これは終わりの始まりですらない。しかしあるいは、始まりの終わりかもしれない。

C言語で配列の添字に負の値を使うのは有りでしょうか。つまり以下のようなコードです。
  a[-1] = 0;
C言語では、配列int a[n]は、*((int*)(((int*)a + n))と等しいということなので、aが配列名、つまり、
  int a[10]
のように定義されたものなら、それは範囲外アクセスになってしまいます。
しかし、
  int b[10]
  int* a = &b[1];
のようなものであれば、*(a-1) = b[0]なので、問題なく動作します。

ということで、一応、言語的には使えなくもない負の添字ですが、不用意に使わない方が良いです。
例えば、以下のように有効な配列の範囲外を指すような使い方は、たとえ値参照しなくても使うべきではありません。
  int b[10]
  int* c = &b[-1];
C言語的には、ポインター 演算は、一度に割り振られた領域と、仮想的な"終端"を越えた1つめ の要素にだけ定義されていて、それ以外では未定義になります。よって、aが有効なアドレス空間の先頭に位置していた場合などに、a[-1]が有効なアドレスになるとは限らないということがありえます。
また、負の添字以前に、配列の添字に符号付き(signed)の型を使うのは、気づきにくい不具合を生む可能性があるという話もあります。例えば、次のようなコード。
#define ARRAY_SIZE 200
int my_array[ARRAY_SIZE];

int array(int n)
{
    if (n >= ARRAY_SIZE) {
        return -1;
    }
    return my_array[n];
}

単純なエラーチェック付きアクセサですが、これまた単純な配慮漏れがあります。
そう、nが負(マイナス値)の時の配慮が入っていないのです。

そのため、nがマイナスで渡されると、エラーチェックをすり抜けて、範囲外アクセスが発生します。この例では参照のみですが、値更新するような関数なら、任意の場所が書き換えられてしまうようなバグです。

さらに、あぶないのは以下のようなコード。
#define ARRAY_SIZE 200
int my_array[ARRAY_SIZE];

int array(int n)
{
    if (n >= ARRAY_SIZE) {
        return -1;
    }
    return my_array[n];
}

int func()
{
    char n = 150;
    printf("%d\n", array(n));
}
今後は、nがchar型になっています。char型は処理系によって、符号付きの場合と、符号なしの場合がありますが、符号ありの場合、150という値は保持されず、負の値になってしまいます。よって、負数を意図して使おうとしているわけではないものの、結果的に負数を使ってしまっています。
gccでは配列添字にcharを使っていると警告されるオプション(-Wchar-subscripts)があります。
charの暗黙の型変換は、いろんなバグの温床になるので、そもそもサイズのような変数の型にcharを使うのは、配列の添字に限らずやめるべきです。この手の変数については、符号なし型、できればsize_t(C言語ならstdlib.hで定義)を使うのが適切です。最新のC言語仕様であるC11の処理系であれば、オブジェクトの最大値を表す型としてrsize_tも使えます。


以上、C言語における配列の添字に負の値、あるいは、符号型を使った時の動作とリスクのお話でした。
この手の話は制約を理解して使えばいいという見方もあるかもしれませんが、ソフトウェア開発の現場等では、あとで別の人がメンテナンスする時の罠になりかねません。実際の現場では、あまりトリッキーなコードを書くのは控えておくのが望ましいですね。

4048919873 【関連記事】
【関連書籍】

本のおすすめ

4274065979

4844337858

482228493X

4904807057

4873114799


プロフィール

  • Author:proger
  • 組み込み関係で仕事してます

ブログ内検索