トピック

概論 ページの先頭へ

テストの設計に使用する情報は、設計モデル、分類子インターフェイス、ステートチャート、コード自体など、さまざまな場所から集められます。このソース文書情報は、いずれかの時点で、次のような実行可能なテストに変換しなければなりません。

  • 実行時にソフトウェアに与える具体的な入力
  • 実行に使用するハードウェアおよびソフトウェアの具体的な構成
  • 実行時に上記構成の初期状態として使用する状態
  • 期待される具体的な結果

ソース文書情報から実行可能なテストに直接進むことも可能ですが、多くの場合、中間ステップを追加した方が実用的です。このステップでは、テスト構想リストテスト構想を書き入れます。その後、このリストを使用して実行可能なテストを作成します。

テスト構想とはページの先頭へ

テスト構想 (テスト要求と呼ばれることもあります) とは、実行可能なテストを簡単に説明する文です。簡単な例として、平方根を計算するテストを考えましょう。次のようないくつかのテスト構想が浮かびます。

  • 0 よりわずかに小さい値を入力として与える
  • 0 を入力として与える
  • 4 (2 の二乗)、16 (4 の二乗) などの完全平方をテストする

上記の各構想は、具体的な入力値と期待される結果を記述して、すぐにでも実行可能なテストに変換することができます。

この具体性の低い中間形式には、次に挙げる 2 つの利点があります。

  • テスト構想は、完全なテストよりも検討しやすく、理解しやすい。特に、その背後にある理由を理解しやすい。
  • テスト構想は、後の「リストを使用したテスト設計」で説明するような、より強力なテストをサポートする。

上に挙げた平方根の例は入力を説明するものばかりですが、テスト構想には実行可能なテストの任意の要素を記述できます。たとえば、「LaserJet IIIp で印刷する」という文は、テストに使用するテスト環境の性質を表しています。「データベースが一杯な状態でテストする」もまた同様です。ただし、これらのテスト構想はそれだけでは非常に不完全です。プリンタには、何を印刷するのでしょうか。また、データベースが一杯な状態で何を行うのでしょう。それでも、これらのテスト構想は、重要な構想が忘れられるのを確実に防ぎます。そして、後にテスト設計で詳しく記述されます。

テスト構想は、多くの場合、誤りモデルに基づいています。誤りモデルとは、どの誤りがソフトウェアに潜んでいる可能性があり、それらの誤りはどのようにすれば最適に発見できるかという概念です。たとえば、境界について考えましょう。平方根関数は、次のように実装できていれば安全です。

double sqrt(double x) {
    if (x < 0) 
      // 符号エラー
    ...

しかし、< が間違って<= と入力されるということもありがちです。多くの人がこの種の間違いを犯すため、これはチェックする価値があります。X の値が 2 だった場合は、間違った式 (x<=0) でも正しい式 (x<0) でも if 文で同じ分岐をたどるため、この誤りは検出できません。同様に、X の値を -5 にしても、この誤りは検出できません。この誤りを検出するには、X の値を 0 にするしかないのです。これで、2 つ目のテスト構想が正当化されます。

この例の誤りモデルは明示的ですが、暗黙的な誤りモデルもあります。たとえば、プログラムがリンク構造を操作する場合は、必ず循環リンクについてテストするのがよい習慣です。間違った循環構造につながる可能性のある誤りは、多数あります。テストの目的では、すべての誤りを列挙する必要はありません。循環に関するテストを実行すれば十分検出できる誤りがいくつかあるということさえ知っておけば十分です。

次のリンク先には、さまざまな誤りモデルからテスト構想を得る方法についての説明があります。最初の 2 つは明示的な誤りモデルを、最後のリンク先では暗黙的な誤りモデルを使用しています。

これらの誤りモデルは、多くの異なる成果物に適用できます。たとえば、1 つ目のリンク先には真偽値式に関する場合の説明があります。真偽値式は、コード、ガード条件、ステートチャートとシーケンス図、メソッドの振る舞いに関する自然言語による説明 (公開された API の中で等) などの中で見つけられます。

まれに、特定の成果物のガイドラインを持つことも助けとなる場合があります。これについては、「../../modguide/md_tstidssttact.htm -- このハイパーリンクは、生成されたこの Web サイト内に存在しませんガイドライン: ステートチャート図とフローチャート図のテスト構想」を参照してください。

1 つのテスト構想リストに多くの誤りモデルから得たテスト構想が含まれ、それらの誤りモデルが複数の成果物に由来する場合もあります。

リストを使用したテスト設計 ページの先頭へ

ここでは、シーケンシャル コレクションに格納された文字列を検索するメソッドのテストを設計しているものとします。この検索では、大文字小文字を区別することも同一視することもできます。メソッドは、最初に一致した文字列の索引を返し、一致する文字列が見つからなかった場合には -1 を返します。

int Collection.find(String string,
                    Boolean ignoreCase);

このメソッドのためのいくつかのテスト構想を次に示します。

  1. 最初の位置で一致する文字列が見つかった
  2. 最後の位置で一致する文字列が見つかった
  3. 一致する文字列は見つからなかった
  4. コレクション内に複数の一致文字列が見つかった
  5. 大文字小文字が区別されない場合: 一致する文字列は見つかったが、それは大文字小文字を区別すると一致しない
  6. 大文字小文字を区別する場合: 完全に一致する文字列が見つかった
  7. 大文字小文字を区別する場合: 大文字小文字を区別しなければ一致するはずだった文字列をスキップした

この 7 つのテスト構想それぞれに 1 つずつ、7 つのテストを実装するのは簡単なことでしょう。しかし、異なる複数の構想を組み合わせて 1 つのテストに入れてしまうことも可能です。たとえば、次のテストは、2 つ目と 6 つ目と 7 つ目のテスト構想に対応します

セットアップ: コレクションを ["dawn", "Dawn"] で初期化
実行: collection.find("Dawn", false)
期待される結果: 戻り値は 1 である ("dawn" がスキップされなかった場合は 0 になる)

テスト構想を具体的にしなければ、複数のテスト構想を組み合わせやすくなります。

3 つのテストだけで上記のすべてのテスト構想を実現することも可能です。では、なぜ 7 つの独立したテストを作成するより、7 つのテスト構想を実現する 3 つのテストを作成した方が望ましいのでしょうか。

  • 多数の単純なテストを作成する作業では、N 番目のテストをコピーし、それを新しいテスト構想を実現するように修正して N+1 番目のテストにするのは一般的なことです。この結果、特に複雑なソフトウェアなどでは、N+1 番目のテストが N 番目のテストとほとんど同じ方法でプログラムを実行することになります。これらのテストは、コード内でほぼ同じ経路を取ります。

    ところが、テストの数が少なく、それぞれが複数のテスト構想を実現する場合には、「コピーして修正する」アプローチは許されません。各テストが異なる方法でコードを実行し、異なる経路を取り、直前に作成したテストとは何らかの点で異なるものになるでしょう。

    そのどこが優れているのでしょうか。テスト構想リストが完璧なもので、プログラムに含まれるすべての誤りに対応するテスト構想が洩れなくリストに入っているのであれば、テストをどのように記述しようが問題ではありません。しかし、リストからは常に、バグを見つけられるはずのいくつかのテスト構想が洩れているものです。各テストが実行する処理を直前に作成したテストとは大きく異なるものにすることにより—つまり、表面上は不必要な多様性を持たせることにより—どれかのテストが全くの偶然でバグに引っかかる可能性が高まります。要するに、テストを少数の複雑なものにすると、必要であることに気付けなかったテスト構想までそのテストで実現できる可能性が高まるのです。

  • ときには、複雑なテストを作成していて、新しいテスト構想を思いつくこともあります。単純なテストを作成していたのでは、行っている作業の大部分が直前のテストとほとんど変わらないため頭が鈍ってしまい、新しいテスト構想を思いつく可能性は低くなります。

一方、複雑なテストは作成しない方がよい理由もあります。

  • 各テストが 1 つのテスト構想だけを実現している場合は、たとえば 2 つ目のテスト構想のためのテストが失敗すれば、最も可能性の高い原因が直ちにわかります。プログラムが、最後の位置で一致する場合をうまく処理していないのです。ところが、1 つのテストで 2 つ目と 6 つ目と 7 つ目のテスト構想を実現していると、誤りを分離するのが難しくなります。

  • 複雑なテストは、理解するのも保守するのも難しくなります。また、テストの意図がぼやけてしまいます。

  • 複雑なテストは、作成も難しくなります。5 つのテスト構想を実現するテストの作成は、多くの場合、それぞれが 1 つのテスト構想を実現する 5 つのテストを作成するよりも時間がかかります。さらに、ミスも犯しやすくなり、5 つのテスト構想を実現していると思っていても実際には 4 つのテスト構想しか実現できていないといった場合も出てきます。

現実的には、複雑さと簡潔さの適切なバランスを見つけなければなりません。たとえば、ソフトウェアに実施する最初のテスト (一般には、スモーク テスト) は、シンプルで、理解しやすく、保守しやすく、最も明らかな問題を補足することを目的としているべきです。一方、もっと後に行うテストはより複雑にするべきですが、それでも保守が難しくなるほど複雑にしてはいけません。

一連のテストが完成したら、「Concepts: Developer Testing」に説明のあるテスト設計の誤りの特性に照らし合わせてチェックするとよいでしょう。

テストを実行する前のテスト構想の利用方法 ページの先頭へ

テスト構想リストは、設計成果物のレビューと検査にも役立ちます。たとえば、設計モデルに次のような Department (部署) クラスと Employee (従業員) クラスの関連を示す部分があるとします。

図 1: Department クラスと Employee クラスの関連

このようなモデルからテスト構想を作成するための規則では、1 つの部署が多数の従業員を抱えているケースを考慮するように要求されるものです。設計に目を通しながら、「ここで、1 つの部署に多数の従業員がいたらどうなるか」と問いつづけることにより、設計または分析のエラーを発見できる場合があります。たとえば、部所間で 1 度に 1 人の従業員しか移動できないことに改めて気付くかもしれません。その会社が多数の従業員を一度に移動させるような組織再編成を実施する傾向がある場合、これは問題になります。

可能性が見過ごされた場合に発生するこのような誤りは、省略の誤り (faults of omission) と呼ばれます。誤り自体と全く同じように、その誤りを検出するテストもテスト作業から省いているかもしれません。例については、参考資料 [GLA81]、[OST84]、[BAS87]、[MAR00]、および省略の誤りがどの程度頻繁に導入に入り込むかを示すその他の研究を参照してください。

設計作業におけるテストの役割については、「概念: テスト ファースト設計」で詳しく説明します。

テスト構想と追跡可能性 ページの先頭へ

追跡可能性は、トレードオフの問題です。その価値は、それを保守するためにかかる費用に見合うでしょうか。これについては、「../../activity/ac_tst_dfnasstrcnds.htm -- このハイパーリンクは、生成されたこの Web サイト内に存在しません作業: 評価と追跡可能性のニーズの定義」の作業中に考える必要があります。

追跡可能性が十分価値がある場合、各テストについて、それを導き出す基となった成果物に遡るのが一般的なやり方です。たとえば、API とそのテストの間に追跡可能性があることもあります。この場合、API が変更されたときには、どのテストを変更すべきかがわかります。コード (その API を実装するコード) が変更された場合に、どのテストを実行すべきかもわかります。テストの意図がわからない場合は、それがテストしようとしていた API を見ればよいでしょう。

テスト構想リストは、上記とは別のレベルの追跡可能性を追加します。テストからそれが実現するテスト構想を追跡し、さらにそのテスト構想から元の成果物まで追跡することができるのです。



Rational Unified Process   2003.06.15