Patterns for Informal Unit Testing

早稲田大学大学院理工学研究化情報科学専攻

太田健一郎

はじめに

 本パターンランゲージはプログラマが正式な単体テストを行う前に用いる非公式なテストについて述べたものである。プロ、アマを問わず多くのプログラマはたとえ正式なテストの知識を持っていなくても、コーディングを行う際に非公式なテストを行っている。この非公式なテストは人によって方法は様々であるが、各人が用いている方法は繰り返されている。これはよく観察するとパターンの形式を取りうることが分かる。自分自身を振り返ると、やはりこの非公式なテストにおいてある種の行動を繰り返し行っていることが分かった。

 本パターンランゲージは筆者が雑誌の投稿プログラムを作成する際に用いているものである。この雑誌の投稿プログラムは毎回クイズが出され、それを解くために用いるものである。このクイズでは例題はコンピュータを使わなくても解くことが出来るが、本題はコンピュータを使わないと時間的にも空間的にもまず解くことが出来ないという性質を持っている。

 本パターンランゲージは実際の業務から得られたものではなく、趣味のレベルのものであるが、正しいクイズの解を得るためには正確なテストを行わなければならないという条件があるので、その厳密性は業務ソフトウェアの開発に必ずしも劣るものではない。

 人によっては本パターンランゲージは当たり前のものであったり、誤ったものであるかもしれない。特にCMMレベル2以上の組織ではコーディングと単体テストに関して、筆者が趣味で行うよりはるかに厳密なプロセスを定めているので、そのような組織では本パターンランゲージは全く意味をなさないかもしれない。それでもなお筆者は本パターンランゲージがCMMレベル1以下の組織やアマチュアのプログラマだけでなく多くの人々に役立つものであると信じる。

 

パターンランゲージの構成

 本パターンランゲージは以下のような4つのテスト実施のパターンと1つのアンチパターンからなる。

各パターンはプログラムの設計とコーディングのプロセスで用いる。各パターンの適用を以下に示す。

 本パターンの対象とするプログラムはクイズという形式を取っており問題が常に提示されている。したがって入力と出力の仕様は定まっており、プログラムがどのように振る舞うべきかの仕様はある程度決定している。また、このクイズは数学やパズルの本に掲載されていてアルゴリムズが知られている場合がある。その場合、まず考えるのは既に同様の問題を解決したプログラム若しくはアルゴリムズを探し、それに改良を加え、自分のプログラムとすることであろう。このプロセスはクイズに限らず、業務でも行われることが多い。この際に多くの人々はクイックハックアンチパターンにはまり込むことが多い。クイックハックアンチパターンではこのプロセスで生じる誤りに対して改善策を提案する。

 採用するアルゴリズムが決定し、設計の仕様も固まり、コーディングに取りかかるところである。メソッドを完全に作成してから単体テストを行うという方法はプログラマにとって不安を伴うことが多い。何らかに手段でコーディング途中に動作確認以上の非公式なテストを行いたい。この際にサンドイッチアプローチパターンが役立つ。サンドイッチアプローチパターンはメソッドの入力ドメインをチェックするメソッドの前半部と、「まだこのメソッドは出来ていない」という例外を発する後半部をはじめに作ることによりコーディング途中のテストを可能とし、なおかつ作成中のメソッドが正式版のソフトウェアに組み込まれることを回避する。

 石橋叩きパターンはサンドイッチアプローチパターンとペアで用いることが多い。石橋叩きパターンではコードを自分自身が安心できる範囲で書くたびにそのコードを通過するテストケースを追加していく。それは1行でも数行でもよい。このパターンによって正式なテストを行う前に非公式にテストスィートはステートメントカバレージを満たす。

 下請けのメソッドについては非公式、公式両方のテストを終え、ついにメインのクイズを解くメソッドを実装している。このメソッドの非公式なテストはサンドイッチアプローチパターンと石橋叩きパターンを組み合わせることによって出来るが、公式なテストについては問題が残っている。すなわちクイズは探索問題であり非常に小さな入力に対しては手作業で出力を求めることが出来るが、一般の値若しくは大きな値については手作業で出力を求めることは困難である。そしてクイズが要求しているのは一般に大きな値の入力である。この際に子供のテストパターンが役立つ。子供のテストパターンはテスト対象のメソッドが採用するアルゴリズムがよく知られていており数学的に証明されているときに用いられる。

 最後に非公式なものであれテストの自動化は必須である。テストが自動化できないとテストを行うのは苦痛となる。3度目の自動化パターンは非公式なテストにおいて、いつ自動化を行うべきかを指摘する。

 

共通の状況

 はじめに述べたように、本パターンランゲージは筆者が雑誌で出されるクイズをプログラムを作成することによって解くときに用いているものである。このプログラムはかなり小規模なものであり、JavaSmalltalkなどのような高級言語を用いれば数100行で書けてしまうものがほとんどである。また個人で作成しているものであり、明確な管理基準はほぼ皆無である。したがって、明確な設計プロセス、コーディングプロセス、テストプロセスが存在する大規模な組織とは全く状況は異なる。状況としてはテストプロセスが明確ではない数10人でのソフトウェアハウス若しくはフリーのプログラマの開発形態に近い。

 このプログラムには正しい仕様が存在し、正しいテストを行わないと正しい答えを得られないという特徴がある。そのためたとえ趣味のレベルであっても同値分割、境界値分析、制御フローなど最低限度の正式なテスト手法の知識は必要である。本パターンランゲージにおける非公式なテストはこれらの正式なテスト手法の一部を利用する。

 クイズに限らずアマチュアプログラマ若しくはテストプロセスが明確でないソフトウェアハウスがプログラムを作成するときにはほぼ同様の状況に置かれることになるであろう。

 

共通のフォース

 本パターンランゲージには共通の状況と同時に共通のフォースが存在する。これらはテストそのもののフォースかもしれない。


アンチパターン:クイックハック

アンチパターンの名前:クイックハック

別名:ハック&コンパイル

頻出スケール:アプリケーション

再構想解の名前:リバースエンジニアリング

再構想解のタイプ:プロセス

根本原因:拙速、無精

対応不全の圧力:機能性の管理

挿話証拠:「ここをこう変えればとりあえず動くだろう。」

 

背景

 対象とする問題の全体像を理解しないで小手先の理解で済ますことはないだろうか。このアンチパターンは小手先の理解をソフトウェア開発に用いる場合に生じるものである。

 

一般形式

 欠陥修正又は機能追加のために自分、若しくは他人が過去に作ったプログラムの変更を行うという状況を考える。このプログラムには文書が一切存在せず、あるのはソースコードのみである。このような状況のとき、このアンチパターンは出現する。

 プログラムに関する文書が存在しない場合、たとえ変更箇所が分かっているとしても、プログラムの全体像を理解出来ず、変更が全体に与える影響が不明確なので、対象個所を適当に修正し、とりあえず動作を確認するという行動に走りがちである。このような行動は正式なテストを否定し、最終的に溶岩の流れ[Brown+98]を生むことになる。

 

病状と結果

 

典型的な原因

 

例外的なケース

 自分専用のソフトウェア、フリーソフトウェアを自己責任で移植する場合などにはこのパターンは許される。しかし、今後のためにも全体像を理解したほうがよいのは明らかである。

 

再構想による解決

 逆説的であるが、全体像を出来る限り把握しようと努力することである。現在は、ソースコードをリバースエンジニアリングすることよって、クラスの協調関係を図示するもの、制御フローグラフを生成するもの、メソッドの仕様書を生成するものなど様々なリバースエンジニアリングツールが存在する。完全とはいえないが、商用では優れたものが数多く存在する。また、フリーでも十分実用になるものは多い。ソースコードとにらめっこして疲労し、結局適当な修正を行うより、まずこれらのツールを使って全体像を把握するほうが近道である。

 

そのほかの視点やスケールへの適応性

 このパターンを根本的に防ぐには上流過程で文書の作成を徹底させることである。だが、この方法は文書とソースコードの乖離という新たな問題を引き起こす。一番よいのはすべての文書とソースコードを効率的に連携させることであるが、これを完全に解決するツールは現在のところ存在しない。

 

 筆者はソースコードだけでは理解が困難な大きさのプログラム、例えば10個以上のクラスからなるモジュールを扱う際にはソースコードを読むと同時にUMLツールのリバースエンジニアリング機能を用い、クラス間の協調関係を理解するようにしている。これによってモジュールの全体像の理解が速まる。また、これによって協調関係がパターンをなしていることも分かるので、製作者の意図もより理解しやすくなる。これはソースコードのみから理解するのはなかなか困難なことである。


パターン名 サンドイッチアプローチ

 

状況

 メソッドの設計も終わり、実装すべき段階に来ている。メソッドの設計仕様からテスト計画も出来上がっている。

 

問題

 メソッドが完成してからテストを行うのではなく、徐々に実装を行いながら非公式なテストを行いたい。

 

フォース

 

解決策

 メソッドの入り口と出口を最初に作成する。入り口では入力ドメインチェックのみを行うステートメントを記述する。

 出口では、「まだこのメソッドは出来ていない」という例外を発して終了するようにする。これによって、仮にこの未完成のメソッドが他のメソッドから呼び出されても、例外が発生しその時点でプログラムは停止することになり、未完成のメソッドを使ってプログラムがそのまま実行を続けてしまうことを防止出来る。メソッドが完成したら、この例外発生ステートメントをコメントアウトする。

 入り口と出口をまず用意することからこのパターンをサンドイッチアプローチと呼ぶ。

 このパターンで行うテストは非公式なものであり、メソッドの完成後、公式なテストを行う必要があることに注意すること。

 実装の順番は次のようになる。

  1. 終了文の直前に、「まだこのメソッドは出来ていない」という例外を発するステートメントを記述する。

  2. この時点でコンパイルが可能となるので、メソッドの呼び出しテストを行い、入力の種類に関わらず、「まだこのメソッドは出来ていない」という例外が送出されることを確認する。

  3. メソッドの先頭に入力ドメインチェックを行うステートメントを記述する。

  4. 正常な入力は受け入れているか、不正な入力は全て弾いているか(無視する、例外を発するなど)Category-Partition パターン[Binder 99]に従ってテストする。

  5. メソッドの本体を記述する。石橋叩きパターン[Oota 2000]に従っても良い。石橋叩きパターンに従った場合、ステートメントを記述するたびに非公式のテストを行うことになり、そのテストは必ず「まだこのメソッドは出来ていない」という例外で終了する。

  6. 本体を記述し、戻り値も仕様に従って変更し、メソッドが完成したら、公式のテストを行う。

 

実装

 Javaにおいて、メソッドに複数の出口がある場合、メソッドの本体をtry節で囲み、finally節に「まだこのメソッドは出来ていない」という例外を記述すると未完成のメソッドで常に「まだこのメソッドは出来ていない」という例外を送出することが可能となる。このイディオムはメソッドの保守の際にも有効である。

 

 0以上の整数を引数とし、各桁の数を足して返すようなメソッドint digit_plus(int a_n)について考える。int digit_plus(int a_n)0以上の整数a_nに対しdigit(0) = 0digit_plus(123) = 6digit_plus(777) = 21のように振る舞うものとする。

 本パターンに従ってメソッドの実装を行いながらテストを行う手順は以下のようになる。

  1. 最初に「まだこのメソッドは出来ていない」という例外を発するステートメントを記述する。

      private static int digit_plus(int a_n) {
        throw new RuntimeException("Method digit_plus is not implemented yet.");
      }
    

  2. いかなる入力値に対しても、「まだこのメソッドは出来ていない」という例外を送出することを確認するテストを行う。

  3. 入り口に入力ドメインを検査するコードを加える。

      private static int digit_plus(int a_n) throws IllegalArgumentException {
    
        // 引数の領域を検査
        if(a_n < 0) {
          throw new IllegalArgumentException("Your input is a_n = " + a_n + ". " +
            "Method digit_plus can accept a_n >= 0.");
        }
        
        throw new RuntimeException("Method digit_plus is not implemented yet.");
      }
    

  4. 入力ドメインに関して、正常値、不正値のテストを行う。Onポイントのa_n = 0a_n = 1Offポイントのa_n = -1などである。a_n < 0IllegalArgumentExceptionが送出されることを確認する。

  5. メソッドの本体を実装する。本体が非常に複雑な場合は石橋叩きパターン[Oota 2000]を使用しても良い。ここではそれほど複雑ではないので、そのまま実装する。メソッド完成後、「まだこのメソッドは出来ていない」という例外はコメントアウトする。

      private static int digit_plus(int a_n) throws IllegalArgumentException {
    
        // 引数の領域を検査
        if(a_n < 0) {
          throw new IllegalArgumentException("Your input is a_n = " + a_n + ". " +
            "Method digit_plus can accept a_n >= 0.");
        }
    
        // 各桁の数を加算
        String a_n_string = Integer.toString(a_n);
    
        int digit_plus = 0;
        for(int a_n_string_index = 0; a_n_string_index < a_n_string.length(); a_n_string_index++) {
          digit_plus += Character.getNumericValue(a_n_string.charAt(a_n_string_index));
        }
        return digit_plus;
    
        /*
        throw new RuntimeException("Method digit_plus is not implemented yet.");
        */
      }
    

  6. 正式なテストを行う。

 

関連するパターン

 本パターンではメソッドの実装途中で非公式のテストを行うため、石橋叩きパターン[Oota2000]と組み合わせて使用される場合が多い。


パターン名 石橋叩き

 

状況

 メソッドの設計も終わり、実装すべき段階に来ているが、今回のメソッドは新しく使うAPIが多い。

 

問題

 メソッドが完成してからテストを行うのではなく、徐々に実装を行いながら非公式なテストを行いたい。

 

フォース

 

解決策

 メソッドを1単位(これは自分の自信の持てる程度によって異なる)書くたびにそのパスを通るテストケースを作成、実行し、実際に正しい操作が行われ、値が返されているのかを確認する。このパターンを用いると正式な単体テストを行う前に100%ステートメントカバレッジが非公式に終了している。

 サンドイッチアプローチパターン[Oota 2000]と同様に、このパターンで行うテストは非公式なものであり、メソッドの完成後、公式なテストを行う必要があることに注意すること。

 

実装

 このパターンにしたがって非公式にテストを行う場合、何度もテストを行うことになるので、自動化ツールは必須である。

 

論理的根拠

 [McConnell 93]ではメソッドに対し、確信を得るまでコンパイルはしてはならないとしており、プログラムが動作すると確信できる前にコンパイルするのは、ハッカー心理の兆候であると述べている。しかし、本パターンでは適当にハックしてから動くかどうかではなく、メソッドの設計はすでに終わっており、コンパイルのたびにテストケースを用意している点が異なっている。テストケースが用意可能であるということは、設計に対する確信が存在しているということである。すなわち、設計が存在しないのではなく、実装時の不安を低減させるために、本パターンを用いるのである。

 

 0以上の整数を引数とし、各桁の数を降順に並び替えた数を返すようなメソッドint down_order(int a_n)について考える。int down_order(int a_n)0以上の整数a_nに対しdown_order(0) = 0down_order(123) = 321down_order(777) = 777のように振る舞うものとする。

  1. 最初にサンドイッチパターン[Oota 2000]などにしたがってメソッドの雛型を構築する。

      static int down_order(int a_n) throws IllegalArgumentException {
        
        // 引数の領域を検査
        if(a_n < 0) {
          throw new IllegalArgumentException("down_oder Method accepts a_n >= 0.");
        }
        throw new RuntimeException("Method down_order is not implemented yet.");
      }
    

  2. サンドイッチパターンにしたがったテストを行う。例えば、次のようなテストスイートを実行する。

    package circulate_sequence;
    
    import java.lang.*;
    import junit.framework.*;
    
    public class Down_Order_Test extends TestCase {
      public Down_Order_Test(String name) {
        super(name);
      }
      public static void main(String[] args) {
        junit.textui.TestRunner.run (suite());
      }
      public static Test suite() {
        return new TestSuite(Down_Order_Test.class);
      }
      public void test0() {
        try {
          Circulate_Sequence.down_order(0);
        }
        catch(RuntimeException e) {
          e.printStackTrace();
        }
      }
      public void test1() {
        try {
          Circulate_Sequence.down_order(1);
        }
        catch(RuntimeException e) {
          e.printStackTrace();
        }
      }
      public void testm1() {
        try {
          Circulate_Sequence.down_order(-1);
        }
        catch(IllegalArgumentException e) {
          e.printStackTrace();
        }
        catch(RuntimeException e) {
          e.printStackTrace();
        }
      }
      public void test123() {
        try {
          Circulate_Sequence.down_order(123);
        }
        catch(RuntimeException e) {
          e.printStackTrace();
        }
      }
    }
    

  3. メソッドを自分の確信できる単位で書くたびにテストケースを作成し、テストを行う。

      static int down_order(int a_n) throws IllegalArgumentException {
    
        // 引数の領域を検査
        if(a_n < 0) {
          throw new IllegalArgumentException("down_oder Method accepts a_n >= 0.");
        }
    
        // 各数を初期化
        int number[] = new int[10];
    
        for(int number_index = 0; number_index < number.length; number_index++) {
          number[number_index] = 0;
        }
    
        // a_nの各桁の数を保存
        String a_n_string = Integer.toString(a_n);
    
        // テスト用
        System.out.println("For a_n = "+ a_n + ", a_n_string = " + a_n_string);
    
        throw new RuntimeException("Method down_order is not implemented yet.");
    
      }
    

  4. 先程と同様のテストケースが使用できる。

  5. テストによって書き足したコードに対して確信が得られれば、次のコードを書き、同様にして非公式にテストを行っていく。

      static int down_order(int a_n) throws IllegalArgumentException {
    
        // 引数の領域を検査
        if(a_n < 0) {
          throw new IllegalArgumentException("down_oder Method accepts a_n >= 0.");
        }
    
        // 各数を初期化
        int number[] = new int[10];
    
        for(int number_index = 0; number_index < number.length; number_index++) {
          number[number_index] = 0;
        }
    
        // a_nの各桁の数を保存
        String a_n_string = Integer.toString(a_n);
    
        for(int a_n_string_index = 0; a_n_string_index < a_n_string.length(); a_n_string_index++) {
          number[Character.getNumericValue(a_n_string.charAt(a_n_string_index))]++;
        }
    
        // テスト用
        System.out.println("For a_n = " + a_n + ", ");
        for(int number_index = 0; number_index < number.length; number_index++) {
          System.out.println("number[" + number_index + "] = " + number[number_index]);
        }
    
        throw new RuntimeException("Method down_order is not implemented yet.");
    
      }
    

  6. メソッドの完成後は正式なテストを行う。

完成したメソッド

  static int down_order(int a_n) throws IllegalArgumentException {

    // 引数の領域を検査
    if(a_n < 0) {
      throw new IllegalArgumentException("down_oder Method accepts a_n >= 0.");
    }

    // 各数を初期化
    int number[] = new int[10];

    for(int number_index = 0; number_index < number.length; number_index++) {
      number[number_index] = 0;
    }

    // a_nの各桁の数を保存
    String a_n_string = Integer.toString(a_n);

    for(int a_n_string_index = 0; a_n_string_index < a_n_string.length(); a_n_string_index++) {
      number[Character.getNumericValue(a_n_string.charAt(a_n_string_index))]++;
    }

    // 降順に変形
    StringBuffer down_order_a_n_string = new StringBuffer();

    for(int number_index = number.length - 1; number_index >= 0; number_index--) {
      while(number[number_index] != 0) {
        down_order_a_n_string.append(number_index);
        number[number_index]--;
      }
    }
    return Integer.parseInt(down_order_a_n_string.toString());
    /*
    throw new RuntimeException("Method down_order is not implemented yet.");
    */
  }

正式なテストケース

package circulate_sequence;

import java.lang.*;
import junit.framework.*;

public class Down_Order_Test extends TestCase {
  public Down_Order_Test(String name) {
    super(name);
  }
  public static void main(String[] args) {
    junit.textui.TestRunner.run (suite());
  }
  public static Test suite() {
    return new TestSuite(Down_Order_Test.class);
  }
  // Onポイント
  public void test0() {
    assert(Circulate_Sequence.down_order(0) == 0);
  }
  // Offポイント
  public void testm1() {
    try {
      Circulate_Sequence.down_order(-1);
      fail("Should raise an IllegalArgumentException.");
    }
    catch(IllegalArgumentException e) {
      assert(true);
    }
  }
  // 一般値
  public void test1() {
    assert(Circulate_Sequence.down_order(1) == 1);
  }
  // 一般値
  public void test123() {
    assert(Circulate_Sequence.down_order(123) == 321);
  }
  // 一般値
  public void test213() {
    assert(Circulate_Sequence.down_order(213) == 321);
  }
  // 一般値
  public void test321() {
    assert(Circulate_Sequence.down_order(321) == 321);
  }
}

 

関連するパターン

 本パターンが成立するためにはメソッドの実装途中でコンパイル可能である必要があり、そのためサンドイッチパターン[Oota 2000]と組み合わせる必要がある。


パターン名 子供のテスト (別名 とりあえず実行)

 

状況

 あるプログラム(モジュール、メソッド)に関して使用するアルゴリズムの正当性は検証されている。しかし、そのアルゴリズムを実装したプログラム(モジュール、メソッド)をテストするために使用する入力と出力の組を手作業で求めるのは時間的、精神的に現実的ではない。

 

問題

 上記の状況のように入力に対する出力を対象プログラム以外で求めるのが現実的ではない場合どのようにテストを行ったら良いか。

 

フォース

 

解決策

 実際に入力列のみからなるテストスィートを作成、実行して、出力結果を分析してそれが正しいことを証明する。

 

適応可能性

 このパターンが有効なのは探索問題などで利用するアルゴリズム自体はそれほど複雑ではなく、出力が決定すれば、その正当性をアルゴリズムにそって検証することが容易な場合である。そして、このパターンを用いて良いのは、手作業で出力を求めるのが現実的に不可能な場合、若しくは求めることはできるが、その求めた出力が誤りを含む可能性が高い場合のみである。

 Trusted System Oracle パターン[Binder 99]などオラクルを使って、手作業以外の方法を使って入力に対する出力を求められる場合、若しくは少し苦労すれば手作業で簡単に誤りなく出力が求められる場合にもこのパターンを用いるのは単なる手抜きである。

 

論理的根拠

 このばからしい程単純なパターンについてBoris Beizerは以下のように述べている[Beizer 90]

 実際にテストを実行して、何が出力されるか見るのが最も簡単である。実にそのとおりだし、別に矛盾したことをいっているわけでもない。要は自己訓練の問題である。出力結果が目の前にある場合、特に内部変数の中間結果まで明らかな場合は、出力を予想してそれが正しいことを検証するよりも、実際の出力結果を分析して、それが正しいことを証明するほうが簡単である。

 子供のテストをオラクルとして使用する場合、予測の手段として利用するのか、あるいは、純粋に子供のテストなのかを明確に区別できないという問題がある。自制心が強く、自己訓練ができており、また、予想結果を検証するための分析方法がドキュメント化されている場合は、子供のテストを利用しても問題はない。

 

 ハノイの塔

 ハノイの塔のアルゴリズムは良く知られており、その正当性は既に証明されている。しかし、そのアルゴリズムを実装したプログラムが正しいかどうかはテストを行わなければ判明しない。以下のJavaプログラムが実装例である。

//タイトル: ハノイの塔

//バージョン:

//著作権: Copyright (c) 2000

//作者: 太田健一郎

//会社名: 早稲田大学大学院

//説明: ハノイの塔の手順を表示する

//タイトル:   ハノイの塔
//バージョン:
//著作権:     Copyright (c) 2000
//作者:       太田健一郎
//会社名:     早稲田大学大学院
//説明:       ハノイの塔の手順を表示する


package hanoi;

import java.lang.*;

public class Hanoi {

  public static void main(String[] args) {

    if(args.length != 1) {
      System.out.println("Usage Hanoi disk_number");
      System.exit(1);
    }

    hanoi(Integer.parseInt(args[0]), "a", "b", "c");

  }

  public static void hanoi(int n, String a, String b, String c) {
    if(n < 1) {
      throw new IllegalArgumentException("Method hanoi needs over 1 for the argument \"int n\".");
    }

    if(n == 1) {
      System.out.println(n + "の円盤を " + a + " から " + b + " に移す");
    }
    else {
      hanoi(n - 1, a, c, b);
      System.out.println(n + "の円盤を " + a + " から " + b + " に移す");
      hanoi(n - 1, c, b, a);
    }
  }
}

 メソッド「hanoi」に対してテストを行う際に可変なのは引数int nである。テストケースとして用意されるのは、Recursive Function Test パターン[Binder 99]に従えば、Onポイントのn = 1Offポイントのn = 0、一般値のn >= 2(これは任意で良い)などである。ここで例えばn = 2の出力は予想可能であるが、n = 6となると手作業で正しい結果を予想するのは難しくなる。このような場合、本パターンを適応する。ただし、上記のような有名なアルゴリムズの場合、Trusted System Oracle パターン[Binder 99]を適応できるかもしれない。

 実際のテストケースは以下のようになる。
//タイトル:   ハノイの塔
//バージョン:
//著作権:     Copyright (c) 2000
//作者:       太田健一郎
//会社名:     早稲田大学大学院
//説明:       メソッドhanoiのテスト


package hanoi;

import java.lang.*;
import junit.framework.*;

public class HanoiTest extends TestCase {
  public HanoiTest(String name) {
    super(name);
  }
  public static void main(String[] args) {
    junit.textui.TestRunner.run (suite());
  }
  public static Test suite() {
    return new TestSuite(HanoiTest.class);
  }
  public void setUp() {
    System.out.println();
  }
  public void tearDown() {
    System.out.println();
  }
  // Onポイント
  public void test1() {
    Hanoi.hanoi(1, "a", "b", "c");
  }
  // Offポイント
  public void test0() {
    try {
      Hanoi.hanoi(0, "a", "b", "c");
      fail("Should raise an IllegalArgumentException.");
    }
    catch(IllegalArgumentException e) {
      e.printStackTrace();
    }
  }
  // 一般値
  public void test2() {
    Hanoi.hanoi(2, "a", "b", "c");
  }
  // 一般値「子供のテスト」パターンを適応
  public void test6() {
    Hanoi.hanoi(6, "a", "b", "c");
  }
}

関連するパターン

 Recursive Function Test パターン[Binder 99]は再帰呼出しを含むメソッドに関して、予想される欠陥とそれを検出するのにどのようなテストケースが必要であるかについて述べている。


パターン名 3度目の自動化

 

状況

 テストの際に手作業でテストデータを何度も入力している。

 

問題

 自動化したほうがよいことは分かっているが、どのタイミングで自動化したらよいのか分からない。

 

フォース

  • 手動でも最初と次ぐらいまでは気を付けてデータを入力する。

  • 同じデータを何度も手動で入力していると疲れて間違いやすくなる。

  • 同じ操作を何度もすることに耐えきれる回数は人により異なる。

  • 手動でのテストはそのうち面倒になり行わなくなる。

  • 手動での入力は使い捨てになりやすい。

  • 自動化の知識は持っている。

 

解決策

 自分が全く同じ操作をして耐えられる回数を超えて実行することが分かれば、自動化できないかどうか考える。テストの規模にもよるが最低でも5回以上の実行で十分自動化の元をとることが出来る。そして、同じ操作に耐えられる回数は更に少なく3回程度である[Kaner+93]。したがって、自動化の目安として3回以上の実行を考えるとよい。

 

実装

 テストの自動化は、簡単なバッチファイルから、ベンダーの高価なテストツールまで様々なものがあるが、最も簡単なものでも最大限の利益を挙げることが出来る。

 

論理的根拠

 [Beizer 95]では手動のテストは意味のないばかりか、危険であり、出来る限り自動化を計るべきであると述べている。

 [Kaner+93]ではテスト担当者は同じ操作を3回以上行ってはいけないと述べている。

 [Gamma+98]に代表されるように現在では単体テストの自動化ツールも数多く存在する。

 

関連するパターン

 [Binder99]Test Harness Designで挙げられるパターンは、すべてテスト自動化に関するものである。


参考文献