Java はじめて25 〜JUnitでのテスト駆動型開発2: テストケース作成〜

イントロダクション

前回のまとめ

JUnitがなんなのか?に関して記載しました。
そして、このフレームワークはテスト(作成したコード)を実行してその動きが想定通りに動いたかどうか?を「assert」を使用して確認できるものです。

動かすためのポイント
junit.jarがビルドパスに設定されている

テストケース作成

今回は、コンストラクタがエラーもなく正常に動いたかどうかの確認を行うところから始めます。

大まかに以下の様な形で実装します。

public class KozaManagerTest {
    /** テスト対象クラス */
    private KozaManager target;
    /**
    *  テストの初期化
    *  各テスト実行前に起動する
    */
    @Before
    public void initClass() {
        target = new KozaManager();
    }
    /**
    * コンストラクタが起動したかどうかを確認する
    * テストケース
    */
    @Test
    public void testIsFile() {
        assertNotNull(target);
    }
}

今回のテストケースを実装していますが、ポイントとしては、以下の点が挙げられます。

  1. @Beforeは各テストケース(メソッド)が起動する前に動く
  2. @Testはテストケース(メソッド)に付ける

上のコードでは、コンストラクタが起動してフィールド変数「target」にインスタンスが設定されているかどうか?(正確には、Nullではない事)を確認します。targetにインスタンスが設定されていない=コンストラクタが起動している最中に例外(Exception)が発生して@Befireのついているメソッドの処理target = new KozaManager()の処理が終わらないうちに(targetに値が設定されないうちに)例外を受け取る処理が動くため、値が設定されない=Nullになるわけです。

<余談>
\@Beforeアノテーションはテストケースが開始される前(Before)に実行覚ます。もし、インスタンスの生成は一回だけでよいのであれば「\@BefreCLass」を使いましょう。JUnit5では\@Before -> \@BeforeEach \@BeforeClass -> \@BeforeAllに代わっているようです。

具体的に

下の動画を見てもらうとわかると思います。

この処理は、コンストラクタで例外を発生させています。

public KozaManager() {
    // 操作するファイルを指定する
    File file = new File("resources/koza.csv");
    try {
        write = new BufferedWriter(new FileWriter(file));
        if (file.exists()) {
            read = new BufferedReader(new FileReader(file));
            throw new IOException("テスト例外");
        }
    } catch (IOException ie) {
        ie.printStackTrace();
        System.out.println("ファイルオープンに失敗しました。" + ie.getMessage());
        System.exit(-1);
    }
}

ここでのポイントは例外が発生した後にアプリ(メインメソッドの処理)を強制終了しているのでJUnitの緑色とか茶色(エラー時)が表示されません。この場合はテストも結果が出ません。

<補足>
ちなみに、コードを実行した後にガベージコレクションでフィールド変数などが解放されるので実装しなくても大丈夫なのですが、テストケースでなんどもKozaMangerクラスのインスタンスを生成するのでC言語的にいうところの「デストラクタ」(コンストラクタの逆=インスタンスを削除する時)が動く時にフィールド変数を解放します。この処理を追加します。ちなみに、「finalize()」はObjectクラスに定義されているメソッドです。そのため「オーバーライド」しています。

「finalize()は書いてはいけない!」みたいな記事がありましたが、「ガベージコレクションに任せるな!」という意味です。
メモリの開放などは、finalize()を明示的に呼び出しましょう

もしくは、インスタンスの開放を明示的にやりましょう

/** デストラクタ */
@Override
protected void finalize() throws Throwable {
    write = null;
    read = null;
}
public static void main(String[] args) throws Throwable {

    try {
       Sample.execute();
    } catch (Exception e) {
       e.printStackTrace();
    } finally {
      // 明示的に呼び出す。
       Sample.finalize();
    }
}

こうすることで、メモリに余計なもの(オブジェクトやインスタンス)を保存しないので、処理が快適に動きます。
「ガベージコレクションがあるからいいや」と思っていると、いつメモリの開放をするかわからないので明示的にメモリの開放処理をしましょう。
ということです。

テストケースの実装

ここからが本当のテスト駆動型開発です、現状はコンストラクタを作成していますが、説明のため。。。言い訳くさい。。。

とりあえず、以下の様に考えます。

「このクラスで要件を満たすために何をしたら良い?」

自分の答えは以下の通りです。

  1. ファイルを操作するためのインスタンスを作成する
  2. ファイルにデータをCSV形式で書き出す。
  3. ファイルが存在するのであれば、それを読み込みデータを保持する

項目1はコンストラクタで実現しているので。今回は2のファイルにCSVデータを書き出す。を実装しようと思います。
設計としては以下の様にします。
<設計>

  1. ファイルの書き出しを行うメソッドはdataOutputという名前にする
  2. 書き出しを行うときはCSV形式で出力する
  3. データを受け取るときは「Dataクラス」で受け取る

この設計から行くと作成するメソッドは「dataOutput」で引数は「Dataクラス」になります。
そして、返却値は「なし=void」です。

なので、テストケースとしては以下の様なコードになります。

/**
  * ファイルにデータを出力し保存するテストケースです。
 */
  public void testFileCeate() {
    Data data = new Data("名前", "パスワード");
    target.dataOutput(data);
}

この様に実装しましたが、「Data」クラスも「dataOutput」メソッドも存在しないのでエラーになります。
なのでこれを「スタブ=空実装(名前だけ、形だけ)」で作成します。そして、作成するDataクラスは以下の様な形で実装します。
これはデータクラスで、内容はフィールドと「Getter(ゲッター)」と「Setter(セッター)」のみになります。

public class Data {
    /** ユーザー名 */
    private String name;
    /** パスワード */
    private String password;
    /**
      * @return the name
      */
      public String getName() {
        return name;
        }
    /**
      * @param name the name to set
      */
      public void setName(String name) {
        this.name = name;
        }
        /**
          * @return the password
          */
        public String getPassword() {
            return password;
        }
        /**
          * @param password the password to set
          */
        public void setPassword(String password) {
            this.password = password;
        }
}

そして、今回作成するメソッドをスタブで作成します。スタブで作成すれば、処理の中身がないけれど、呼び出し元ではエラーなく呼ぶことができます。なのでIN(引数)とOUT(返り値)だけ定義してあれば良いのです。
さらに、データクラスをインポートします。
import jp.zenryoku.apps.atm.data.Data;

<テストケースの実装と作成するクラス>

<スタブメソッド>※返り値なしの場合です。

/**
* データクラスを受け取り、CSVファイルを出力する(書き出しを行う)
* @param data コーダー銀行のユーザー情報
*/
public void dataOutput(Data data) {
}

次回は、このスタブで要件を満たすことができるかどうか考え、必要な実装を行いたいと思います。

Java はじめて26 〜JUnitでのテスト駆動型開発3: クラスの実装〜

ここの部分が実装になります。

実装したらテストして、修正…を繰り返す形で実装します。テスト駆動だと作った処理が想定通りか確認する事とテストが同じなので一手間省けます。

でわでわ。。。

<<< 前回 次回 >>>

<Java関連の動画リスト>

<JUnit関連の動画リスト>



投稿者:

takunoji

音響、イベント会場設営業界からIT業界へ転身。現在はJava屋としてサラリーマンをやっている。自称ガテン系プログラマー(笑) Javaプログラミングを布教したい、ラスパイとJavaの相性が良いことに気が付く。 Spring framework, Struts, Seaser, Hibernate, Playframework, JavaEE6, JavaEE7などの現場経験あり。 SQL, VBA, PL/SQL, コマンドプロント, Shellなどもやります。

コメントを残す