第10週目演習

下準備

テストドライバの導入

第10回目分のテストドライバを導入する。以下の手順で行う。

  1. ダウンロード のページを開く (ここをクリック)
  2. プロジェクト「java20XX」にある「test」の左側の「+」をクリック
  3. ツリーが展開されるので「install-libraries.xml」を右クリック
  4. 「実行(R)」にマウスカーソルを合わせる
  5. 「1 Ant ビルド」をクリック
  6. 「コンソール」タブに"BUILD SUCCESSFUL"と表示されれば成功
  7. eclipseの画面でプロジェクト「java20XX」を右クリック
  8. メニューが表示されるので、「更新」あるいは「最新表示」をクリック
  9. "week10.zip" をデスクトップなどにダウンロード
  10. eclipseの画面でプロジェクト「java20XX」を右クリック
  11. メニューから「インポート(I)」を選択
  12. 「インポート」ウィンドウが表示されるので、「Zip ファイル」を選択
  13. 「次へ(N)」をクリック
  14. 宛先フォルダー(L): が「java20XX」になっていることを確認
  15. From zip file: の右側にある「参照(R)...」あるいは 「ブラウズ(R)...」をクリック
  16. ファイルダイアログが表示されるので、ダイアログ内に表示されたダウンロードしたファイルをダブルクリック
  17. 前の画面に戻るので、From zip file: のエリアに正しいパスが入力されていることを確認
  18. フォルダ「/」の左にチェックがついていることを確認 (ついていなければチェックボックスをクリック)
  19. 「警告を出さずに既存リソースを上書き」にチェックがついていることを確認 (上書きしたくないファイルがある場合はチェックを外す)
  20. 「終了 (F)」をクリック

第10週目テストドライバの導入に成功すると、java20XX プロジェクトの test フォルダに j1.lesson10.xml というファイルが作成される。

パッケージの作成

過去の演習を参考にして、「j1.lesson10」というパッケージを作成する。

擬似コードを用いたプログラミング

課題0901(PascalTriangle)を擬似コードを用いて作成する。

擬似コードの作成

課題0901(PascalTriangle)では、コンソールから全体の要素数と選び出す要素数を入力させ、その組み合わせの総数を表示するプログラムを作成した。課題の仕様を元に擬似コードを作成する。

プログラム全体の擬似コード

仕様を元にプログラム全体の擬似コードを書くと、以下のようになる。

プログラム全体
    print "全体の要素数を入力:"
    n = コンソールからの入力 (整数)
    print "選び出す要素数を入力:"
    r = コンソールからの入力 (整数)
    print "nからrを選び出す組み合わせはcombination(n,r)通り"

以上である。ここで、combination(n,r)を明確にしなかったため、次の擬似コードで明確にする。

combinationを計算する手順の擬似コード

組み合わせの総数を計算する関数 combination は、以下のような式であった。

combination(n,r) = 1  (r = 0)
combination(n,r) = 1  (r = n)
combination(n,r) = combination(n-1, r-1) + combination(n-1, r)  (r != 0 かつ r != n)

これを元に擬似コードを作成する。

combination(n, r)
    if r が 0
        結果は 1
    else if r が n と等しい
        結果は 1
    else
        結果は combination(n-1, r-1) + combination(n-1, r)

しかしこれだけでは完全でなく、入力する値 n と r に制約を設けてやる必要がある。

今回は、この制約を満たしていない入力が来たら 0 を返すことにする。

combination(n, r)
    if n が 0 未満
        結果は 0
    else if r が 0 未満 または r が n より大きい
        結果は 0

    if r が 0
        結果は 1
    else if r が n と等しい
        結果は 1
    else
        結果は combination(n-1, r-1) + combination(n-1, r)

この擬似コードを元にプログラムを作成する。

骨格の作成

クラスの作成

以下の手順で、パッケージ「j1.lesson10」に「CombinationSafe」クラスを作成する。

  1. 先ほど作成したパッケージ 「j1.lesson10」の上で右クリック
  2. マウスカーソルを「新規」に合わせる
  3. 「クラス」をクリック
  4. クラス名は CombinationSafe とする
  5. 先週までと同じ手順でクラスを作成する

擬似コードの貼り付け (骨格のみ)

ここまでに、2つの擬似コードを作成した。その擬似コードの名前をクラス内にコメントとして貼り付ける。ただし、擬似コードの「プログラム全体」ではコンソールからの入力を行っていたため、import java.io.*; をつける。

package j1.lesson10;

import java.io.*;

public class CombinationSafe {
    // プログラム全体
    // combination(n, r)
}

mainメソッドの作成 (骨格のみ)

擬似コード「プログラム全体」に合わせて、クラス「CombinationSafe」内にpublic static void main(String[] args) から始まるメソッドを作成する。ただし、擬似コード「プログラム全体」ではコンソールからの入力を行っていたので、throws IOExceptionをつける。

combination メソッドの作成 (骨格のみ)

擬似コード「combination(n,r)」に合わせて、クラス「CombinationSafe」内に 「combination」という名前を持ち、仮引数に2つのint型の変数をとる(int,int)メソッドを作成する。また、メソッドの戻り値はintとする。骨格だけでよいのでメソッドの中身は return 0 だけでよい。

仮引数の名前は何でも良いが、ここでは擬似コードに併せて順に n, r という名前をつけることにする。

全体の骨格

ここまでのプログラムの骨格は以下のようになる。

package j1.lesson10;

import java.io.*;

public class CombinationSafe {

    // プログラム全体
    public static void main(String[] args) throws IOException {
    }
    
    // combination(n, r)
    public static int combination(int n, int r) {
        return 0;
    }
}

main メソッドのほかに、combination(int,int) メソッドを作成している。その際、戻り値が void 型でないメソッドは、return文を入れないとコンパイルエラーになるため、ダミーの戻り値を用意している。

骨格テスト

ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。

pr01combinationsafe-skel.xml を実行する。

骨格テストを行った際に緑のバーが表示されれば、外側から見たプログラムの骨格は正しくなっている。

赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「実行」ボタンをクリックする。

メッセージ 詳細
(クラス名), existence j1.lesson10 に CombinationSafe クラスが存在していない。パッケージやクラス名を確認
(メソッド名), existence 指定されたメソッドが存在しない
(メソッド名), public メソッドを作る際に public が抜けている
(メソッド名), static メソッドを作る際に static が抜けている
(メソッド名), type <T> メソッドを作る際に戻り値の型を間違えている (正しくは <T>)
(メソッド名), throws java.io.IOException メソッドを作る際に throws IOException が抜けている

プログラムへの変換

combinationメソッドの実装

メソッド combination の中身を記述する。

まずは擬似コードをコメントとして貼り付ける

public static int combination(int n, int r) {
    // if n が 0 未満
        // 結果は 0
    // else if r が 0 未満 または r が n より大きい
        // 結果は 0

    // if r が 0
        // 結果は 1
    // else if r が n と等しい
        // 結果は 1
    // else
        // 結果は combination(n-1, r-1) + combination(n-1, r)

    return 0;
}

次に、擬似コードに対する命令を書く。書き終わったら最後に入れていたダミーの return 0; はいらないので削除する。

// combination(n, r)
public static int combination(int n, int r) {
    // if n が 0 未満
    if (n < 0) {
        // 結果は 0
        return 0;
    }
    // else if r が 0 未満 または r が n より大きい
    else if (r < 0 || r > n) {
        // 結果は 0
        return 0;
    }

    // if r が 0
    if (r == 0) {
        // 結果は 1
        return 1;
    }
    // else if r が n と等しい
    else if (r == n) {
        // 結果は 1
        return 1;
    }
    // else
    else {
        // 結果は combination(n-1, r-1) + combination(n-1, r)
        return combination(n - 1, r - 1) + combination(n - 1, r);
    }
}

main メソッドの実装

続けて、先ほど作成した CombinationSafe クラスの main メソッドの中身を記述する。

まずは擬似コードをコメントとして貼り付ける。

// プログラム全体
public static void main(String[] args) throws IOException {
    // print "全体の要素数を入力:"
    // n = コンソールからの入力 (整数)
    // print "選び出す要素数を入力:"
    // r = コンソールからの入力 (整数)
    // print "nからrを選び出す組み合わせはcombination(n,r)通り"
}

次に、擬似コードに対する命令を書く。コンソールへの入力をする際には BufferedReader reader = ... というものを用意する必要があるが、これはJava特有の作業なので擬似コードには含まれない。注意すること。

// プログラム全体
public static void main(String[] args) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    // print "全体の要素数を入力:"
    System.out.print("全体の要素数を入力:");
    // n = コンソールからの入力 (整数)
    int n = Integer.parseInt(reader.readLine());
    // print "選び出す要素数を入力:"
    System.out.print("選び出す要素数を入力:");
    // r = コンソールからの入力 (整数)
    int r = Integer.parseInt(reader.readLine());
    // print "nからrを選び出す組み合わせはcombination(n,r)通り"
    System.out.println(n + "から" + r + "を選び出す組み合わせは" + combination(n, r) + "通り");
}

全体的には下記のようなプログラムにする。ただし、一文書くごとにCtrl+Sで保存とコンパイルを行い、常に文法エラーが発生していないことを確認すること。

package j1.lesson10;

import java.io.*;

public class CombinationSafe {

    // プログラム全体
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        // print "全体の要素数を入力:"
        System.out.print("全体の要素数を入力:");
        // n = コンソールからの入力 (整数)
        int n = Integer.parseInt(reader.readLine());
        // print "選び出す要素数を入力:"
        System.out.print("選び出す要素数を入力:");
        // r = コンソールからの入力 (整数)
        int r = Integer.parseInt(reader.readLine());
        // print "nからrを選び出す組み合わせはcombination(n,r)通り"
        System.out.println(n + "から" + r + "を選び出す組み合わせは" + combination(n, r) + "通り");
    }

    // combination(n, r)
    public static int combination(int n, int r) {
        // if n が 0 未満
        if (n < 0) {
            // 結果は 0
            return 0;
        }
        // else if r が 0 未満 または r が n より大きい
        else if (r < 0 || r > n) {
            // 結果は 0
            return 0;
        }
    
        // if r が 0
        if (r == 0) {
            // 結果は 1
            return 1;
        }
        // else if r が n と等しい
        else if (r == n) {
            // 結果は 1
            return 1;
        }
        // else
        else {
            // 結果は combination(n-1, r-1) + combination(n-1, r)
            return combination(n - 1, r - 1) + combination(n - 1, r);
        }
    }
}

プログラムの実行

先週までと同じ手順でプログラムを実行する。

入力として順に 10, 5 を与えてやると、以下のように表示される。

全体の要素数を入力:10
選び出す要素数を入力:5
10から5選び出す組み合わせは252通り

解説

擬似コードがあるため割愛する。

ソフトウェア・テスト

単体テスト

メソッドcombination(int, int) に対する単体テストを行う。

テストケースの作成

以下の制約を元に、同値クラスを判定する。

すると、n は次のように分けられる。

n の範囲 意味
~ -1 無効同値クラス
0 ~ 有効同値クラス

また、r は次のように分けられる。

r の範囲 クラス
~ -1 無効同値クラス (1)
0 ~ n 有効同値クラス
n + 1 ~ 無効同値クラス (2)

すると、境界値分析法により次のような値を与えればよいという指針を作成できる。

さらに、今回は分岐のあるプログラムであるので、次のようなテストケースも含めることにする。

これを元に、テストケースを作成する。

n r 期待する値 備考
-5 -5 0 n 無効一般値
-1 -1 0 n 無効境界値
0 -5 0 r 無効一般値
0 -1 0 r 無効境界値
0 0 1 n 有効下限境界値
5 0 1 n 有効一般値, r 有効下限境界値
5 1 5 r = 0 の検査 + 1
5 2 10 n 有効一般値, r 有効中間値
5 4 5 r = n の検査 - 1
5 5 1 n 有効一般値, r 有効上限境界値
5 6 0 n 有効一般値, r 無効下限境界値
5 10 0 n 有効一般値, r 無効一般値

JUnit を用いたテストの実行

テストを行うためのプログラムを、Eclipseに標準で付属しているJUnitというツールを用いて作成する。

テストケースクラスの生成

以下の手順でテストケース用のクラスを作成する。

  1. CombinationSafe.java を右クリック
  2. 「新規 > テストケース」をクリック
  3. 1回目は「JUnit ライブラリー ...」 というダイアログが表示されるので「はい(Y)」を選択
  4. 「新規 JUnit テスト・ケース」というウィンドウが開かれるので、次の作業を行う (図1)
    1. 名前が「クラス名+Test (CombinationSafeTest)」になっていることを確認
    2. 「どのメソッド・スタブ...」で、
      • 「public static void main(String[] args)」にチェック
      • 「TestRunner ステートメントの追加」にチェック
      • 「TestRunner ステートメントの追加」の右側のボックスを「swing ui」にする (swing u までしか見えないかもしれない)
    3. 「次へ」をクリック
  5. 次の画面に移動するので、次の作業を行う (図2)
    1. 使用可能なメソッドから、単体テスト対象のメソッドをチェック
      • ここでは combination(int,int) のみチェック
    2. 下部にあるチェックボックスのチェックを全て外す
    3. 「終了」をクリック


図1


図2

上記の手順が完了すると、以下のようなソースコードが生成される。

package j1.lesson10;

import junit.framework.TestCase;

public class CombinationSafeTest extends TestCase {

    public static void main(String[] args) {
        junit.swingui.TestRunner.run(CombinationSafeTest.class);
    }

    public void testCombination() {
    }

}

テストプログラムの実装

先ほど作成したソースコードに、テストプログラムを書き込んでいく。

先の手順で作成したクラスの各メソッド内では、次のようにテストを記述できる。

assertEquals(期待する値 , クラス名.メソッド名(引数));

例えば、以下のテストケースを実行する場合を考えると、

テスト対象 n r 期待する値 備考
combination (int,int) -5 -5 0 n 無効一般値

次のように書けばよい。

assertEquals(0, CombinationSafe.combination(-5, -5));

今回はcombination(int,int)に対するテストなので、以下の中にテストケースを次々と書いていく。

public void testCombination() {
}

先ほど作成したテストケースを全て書くと、以下のようになる。

package j1.lesson10;

import junit.framework.TestCase;

public class CombinationSafeTest extends TestCase {

    public static void main(String[] args) {
        junit.swingui.TestRunner.run(CombinationSafeTest.class);
    }

    public void testCombination() {
        assertEquals(0, CombinationSafe.combination(-5, -5));
        assertEquals(0, CombinationSafe.combination(-1, -1));
        assertEquals(0, CombinationSafe.combination(0, -5));
        assertEquals(0, CombinationSafe.combination(0, -1));
        assertEquals(1, CombinationSafe.combination(0, 0));
        assertEquals(1, CombinationSafe.combination(5, 0));
        assertEquals(5, CombinationSafe.combination(5, 1));
        assertEquals(10, CombinationSafe.combination(5, 2));
        assertEquals(5, CombinationSafe.combination(5, 4));
        assertEquals(1, CombinationSafe.combination(5, 5));
        assertEquals(0, CombinationSafe.combination(5, 6));
        assertEquals(0, CombinationSafe.combination(5, 10));
    }

}

テストの実行

これまでの流れで作成したテストケースは、以下の手順で実行できる。

  1. CombinationSafeTest.java を右クリック
  2. 「実行 > 2 JUnit テスト」をクリック

すると、ウィンドウの左側にある領域のタブに「JUnit」というタブが作成される (2 JUnit Testで実行した場合のみ)。バーが緑色になったら成功である。赤くなってしまったらプログラムを確認すること。左側を元の画面に戻すには、「パッケージ・エクスプローラー」というタブをクリックすればよい。

「1 Java アプリケーション」で実行した場合は、エラー時に次のような画面になるはずである。


この項目を簡単に紹介する。

  1. Error (例外が発生) または Failure (assert~の失敗) が発生した項目が一覧表示される
  2. テストが失敗となった理由が表示される
  3. テストが失敗した位置が表示される

機能テスト

機能テストを行う際は、これといって有用なツールはない。そこで実際に CombinationSafe を実行し、コンソールからいくつか値を与えてやればよい。

用意されたテスト

今までどおり、こちら側で用意したテストも行ってもらう。

単体テスト

「CombinationSafe に対する単体テスト」 を実行。演習と同様のテストを行う。

失敗時のメッセージには次のようなものがある。

メッセージ 詳細
(メソッド名), 期待された結果と異なります (メソッド名)を起動した結果が期待された結果と異なる。テスト項目を参照

機能テスト

「CombinationSafe に対する機能テスト」 を実行。演習と同様のテストを行う。

失敗時のメッセージには次のようなものがある。

メッセージ 詳細
余計な入力を待っていると考えられます コンソール入力を取得する命令を必要以上に行っていないか確認
次の入力を変換できませんでした (???) ??? を取得しようとして失敗している。コンソール入力を取得する命令を確認
期待された結果と異なります 出力された結果が期待された値と異なる。「期待された値」と「実際の値」を比較 (エラーメッセージが出力されているエリアの下のほうにあるバーをスクロールさせれば見ることができる) し確認。他にも直接プログラムを実行して結果を調べたり、エラーメッセージの2行目にあるat以下を参考にプログラムを見直す
無限ループの可能性 一定時間内にプログラムが終了しなかった。ループが無限に続いていないか確認

メタテスト

「CombinationSafe に対するメタテスト」 を実行。テストケースに対するテストを行う。

失敗時のメッセージには次のようなものがある。

メッセージ 詳細
(クラス名) テストが失敗している
testcase (メソッド名).(引数) 指定されたメソッドを指定された引数でテストしていない