Java はじめて28 〜JUnitでのテスト駆動型開発5: ファイル出力の実装〜

イントロダクション
前回は実装するべきクラスの実装を行いました。しかし、ファイル入出力の部分は手付かずだったので、その部分を実装しようと思います。

<JUNITの使い方>


今回は、ファイル出力処理を実装します。つまりテストケースの作成と実行を行います。記事の下に動画があるので、参考にどうぞ、

ファイル出力の実装

ファイル出力と言っても、ファイルの作成自体はすでに実装済みなのでデータをファイルに書き込む処理の実装ということになります。
<前回までにできていること>

  1. ファイル作成(koza.csv)
  2. ファイルの存在チェック(KozaManager#isFile)

補足として、ファイルの作成 => new File()ファイルへの書き込み => Writer.write()

ここまでが実装したものになります。ちなみにテストコードは下の様になります。

public class KozaManagerTest {
    /** テスト対象クラス */
    private KozaManager target;
    /**
    *  テストの初期化
    *  各テスト実行前に起動する
    */
    @Before
    public void initClass() {
        target = new KozaManager();
    }

    /**
     * テスト対象クラスのメモリ開放
     */
    @After
    public void terminate() {
        target.finalize();
    }
    /**
    * コンストラクタが起動したかどうかを確認する
    * テストケース
    */
    @Test
    public void testIsInstance() {
        assertNotNull(target);
    }
    /**
    * ファイルの存在チェック処理の確認
    */
    @Test
    public void testIsFile() {
        assertTrue(target.isFile());
    }
    /**
    * ファイルにデータを出力し保存するテストケースです。
    */
    public void testFileCeate() {
        Data data = new Data("名前", "パスワード");
        try {
            target.dataOutput(data);
        } catch(IOException ie) {
            ie.printStackTrace();
            fail();
        }
    }
}

ここでの注意点は、「@Test」のついていないテストケース(メソッド)がファイル出力のテストケースになります。
なので、今回はこのコードを実行しようと思います。
早速「@Test」を上のテストケースにつけます。

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

想定通りにいかない原因

製造中につきものの「動かない。。。」にあたります。
本当であれば、テストコードの中でファイルの中身を確認するのですが、ファイル出力後のテストケースも考えていないので、とりあえず。。。と言ったところです。
さて、想定通りにいかない原因を考えます。というか探します。本体のコードを見直してみると。。。

public void dataOutput(Data data) throws IOException {
    // おおよそのデータサイズを指定すると余計なメモリを使用しなくて済む
    StringBuilder build = new StringBuilder(50);
    // ヘッダー部分の出力
    build.append(this.createCSVHeader());
    // ファイル出力処理
    write.append(build.toString());
    // StringBuilderのクリア
    build.setLength(0);
    // データ部分の出力
    build.append(data.getName() + ",");
    build.append(data.getPassword());
}

「あー、これはファイルの中身がなくて当然だなぁ」と気がつきます(笑)
上のコードでは、出力用文字列作成用のオブエジェクト(StringBuilder)を作成しているだけです。下のようにStringBuilderに文字列を追加しないと出力するべきデータがない状態になります。

build.append(data.getName() + ",");
build.append(data.getPassword());

なので修正します。

public void dataOutput(Data data) throws IOException {
    // おおよそのデータサイズを指定すると余計なメモリを使用しなくて済む
    StringBuilder build = new StringBuilder(50);
    // ヘッダー部分の出力
    build.append(this.createCSVHeader());
    // ファイル書き込み処理
    write.write(build.toString());
    // StringBuilderのクリア
    build.setLength(0);
    // データ部分の書き込み
    build.append(data.getName() + ",");
    build.append(data.getPassword());
    write.write(build.toString());
}

そして、ファイルに出力したらファイルの入出力で使用しているReaderとWriter、そのほかのフィールド変数の後始末をする必要があります。でないとメモリ上にインスタンスが残ってしまいます。Javaの場合はガベージコレクションで最終的にメモリは解放されますが、ここでは明示的に「KozaManger」クラスのインスタンスが解放されるときに各フィールドの解放を行います。(デストラクタに実装)※finalize()を呼び出しましょう。

/** デストラクタ */
@Override
protected void finalize() throws Throwable {
    // フィールド変数の後始末
    file = null;
    read.close();
    write.close();
    write = null;
    read = null;
}

それでは、改めてテストを実行してみます。しかし想定通りに行きませんでした。。。
ファイルは出力しているのですが、肝心の中身が出力されていない状態でした。
原因としては「ファイルを閉じていない」と「ファイルを開くときに追記モードで開いていない」ことでした。
そして、最終的には以下の様なコードになりました。

public void dataOutput(Data data) throws IOException {
    if (file.canWrite() == false) {
        throw new IOException("ファイルの書き込みができません: " + file.getAbsolutePath());
    }
    // おおよそのデータサイズを指定すると余計なメモリを使用しなくて済む
    StringBuilder build = new StringBuilder(50);
    // ヘッダー部分の出力
    build.append(this.createCSVHeader());
    // ファイル書き込み処理
    write.write(build.toString());
    write.newLine();
    // StringBuilderのクリア
    build.setLength(0);
    // データ部分の書き込み
    build.append(data.getName() + ",");
    build.append(data.getPassword());
    write.write(build.toString());
    write.newLine();
    write.close();
}

というわけで、テストをしながら実装するのでやりやすい(色々と試しやすい)実装方法だと思います。
ちなみにテストケースは、色々と試したので少々変わっています。

@Test
public void testFileCeate() {
    Data data = new Data("test", "passwd");
    try {
        target.dataOutput(data);
    } catch(IOException ie) {
        ie.printStackTrace();
        fail("ファイル入出力に問題があります。");
    } catch(Exception e) {
        e.printStackTrace();
        fail("想定外のエラーが起きました。");
    }
}

そして、本体のクラス(メソッド)も同様に修正が入っております。最終的に以下の様な実装になりました。

public void dataOutput(Data data) throws IOException {
    if (file.canWrite() == false) {
        throw new IOException("ファイルの書き込みができません: " + file.getAbsolutePath());
    }
    // おおよそのデータサイズを指定すると余計なメモリを使用しなくて済む
    StringBuilder build = new StringBuilder(50);
    // ヘッダー部分の出力
    build.append(this.createCSVHeader());
    // ファイル書き込み処理
    write.write(build.toString());
    write.newLine();
    // StringBuilderのクリア
    build.setLength(0);
    // データ部分の書き込み
    build.append(data.getName() + ",");
    build.append(data.getPassword());
    write.write(build.toString());
    write.newLine();
    write.close();
}

後、ファイルを開くときに追加書き込みモードで開く必要があるので。。。(今回の実装ではデータの保存を行うのでフィールドで保持する必要があるため)コンストラクタの修正も行いました。

// 修正前のコード
write = new BufferedWriter(new FileWriter(file));
// 修正後のコード
write = new BufferedWriter(new FileWriter(file, true));

実行結果は以下の通りです。

次回は、各処理を(ファイル存在チェック〜ファイル出力)テストします。最終的な確認のコードです。

でわでわ。。。

<<< 前回 次回 >>>

<Java関連の動画リスト>

<JUnit関連の動画リスト>



投稿者:

takunoji

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

コメントを残す