Java はじめて23 〜テスト駆動型開発を行う〜

イントロダクション

テスト駆動開発と聞いて、「すげーことやんのか?」と思った方ごめんなさい。
シンプルに実装の順番が違うだけです。
<サンプル>

今までの開発の場合

  1. 設計: プログラムの設計(アプリケーションの設計)
  2. 実装: 設計に従い、プログラムを実装(アプリケーションのの実装)
  3. ユニット・テストの作成: 作成したプログラムがちゃんと動くかチェックするためのテストケース(テスト仕様)を作成
  4. ユニット・テスト実施(単体テスト)
  5. 結合テスト: 今まで作成したプログラムを全て繋げて、アプリケーションをして動かしてテスト仕様通りに動くか確認
     ※画面の操作を行うようなテストは、結合テストです。

テスト駆動開発の場合

  1. 設計: プログラムの設計(アプリケーションの設計)、テスト仕様の作成
    テスト仕様も作成するので、詳細な設計が必要になります。なので、「このフラグはどこのクラスでもつの?」などの設計不足が減ります。

  2. ユニット・テスト: これから作成するプログラムがテスト仕様通りに動くことを確認するプログラム、テストケースを作成
    プログラムレベルでのテストケースを作成するので、「どのように動けばよいか?」が設計レベルで明確になります。

  3. 実装: 設計に従い、プログラムを実装(アプリケーションのの実装)、ただしテストケースが全て通るように作成する
    テストケースが通るように作成するので、「ゴール」が見える状態で実装することができる。

  4. 結合テスト: 今まで作成したプログラムを全て繋げて、アプリケーションをして動かしてテスト仕様通りに動くか確認

上記のように作業工程が1つ少ない形での作業になります。仕様(このように動く、という取り決め)が先行して作成されるので出来上がるアプリケーションも、想定通りに作成することができ、作業工程が1つ減るのでスピードも上がります。
そして、実装とユニットテストが癒着しているので、実装で起動確認を行うときにユニットテストができます。

テストケースの作成

初めに、JUnitのライブラリを参照する様に設定します。

やり方は、以下を参照下さい。

開発環境構築~Windows版Eclipseの設定~

開発環境のセットアップを初めから記載しています。使用しているのはEclipseです。

今回は、JUnitを使用して以下のことを実行します。

  1. テスト仕様の作成
  2. テストケース(コード)の作成
  3. 本番のクラスを作成する

テスト駆動型開発

世間ではいろいろなことが言われていますが、シンプルに「テスト仕様から作成する方法」という意味で記載しています。
シンプルに「テスト仕様から作成する」ということはどいうことかに関して、ちょいと記載します。
早い話が、「このクラスはどう動けば良いの?」から考える実装方法だと思っています。「テスト駆動型開発」という言葉を作った人間ではないので、この様な書き方になります。

ちなみに、JUnitが使用できるかどうかに関しては下の様に「JUnit」がワークスペース上に表示されていれば大丈夫です。下の画像には「JUnit5」があります。

例外として、存在するけど動かないということもありますが、それはエラーが出力され、コンソールに「XXXException」と表示されるのでそれをインターネットで調べてみると解決します。
ちなみに、自分の場合は以下の様にアノテーションをつけてやると動きました。Springなどのフレームワークを使用している場合は、それぞれのテストさ癖方法があるのでそちらを参照されたし。。。JUnit5を使用している場合です

@@RunWith(JUnit5.class)
public class MyTest {
テストケース
}

テスト仕様を考える

前回作成した、仕様と設計より「口座管理クラス」を作成することにしました。この口座管理クラスはファイルを使用してデータを保存したり、更新したりします。

本番で使用するクラスとテストクラスを分けるために以下の様に完全クラス名は同じだけどルートフォルダが違う形で作成します。
今回作成するクラスは、KozaManagerクラスです。

そして、テストクラスは下の様に「test」フォルダ以下に作成します。

テスト仕様

テスト仕様というと堅苦しい感じがしますが、早い話が「どう動けば良いか?」を決めるところです。ちょっと慎重にいきます。
しかし、今回はファイルに出力するだけなので深く考えません。

ちなみに、深く考えると下の様になります。

テスト仕様

  1. 操作するファイルが存在しなければ、作成し、存在すればそのまま参照する
  2. 同様にファイルへ(口座の)ユーザー情報をファイル(CSV形式)への出力、保存ができること
  3. 同様にファイルの読み込みができること
  4. 読み込んだファイルのデータをユーザー情報クラスで取得できること(同様にファイルへ出力できること)

CSVファイルを作成

CSVファイルはカンマ区切りのテキストファイルです。

項目名1, 項目名2 , ....
1行目データ1, 1行目データ2, ...
2行目データ1, 2行目データ2, ...

この様な形で、データを保存します。今回は、口座情報なのと最小限度の情報を登録するので、ユーザーとパスワード、預金額をデータとして保存します。

早速テストが動くか確認します。
```java
public class KozaManagerTest {
/** テスト対象クラス */
private KozaManager target;
/**
* テストの初期化
* 各テスト実行前に起動する
*/
// @Before
// public void initClass() {
// target = new KozaManager();
// }
/**
* isFileメソッドのテストケース
*/
@Test
public void testIsFile() {
System.out.println("Hello JUnit");
}
}
```
ちなみに、コンストラクタを動かしたくないので今はコメントアウトしています。まずは、テストが動くか確認します。

テストを実行するときは下の様に表示されます、

<JUnitでデバックをしてみた>

他にも、こんなことをやってみました。

## JUnitでテスト作成
だいぶ説明がザツだったので、改めます。。。
### テスト作成準備
まずは、上記のようにテストクラスとテスト対象クラスを準備します。
テスト対象クラスは、まずメソッドの側だけ作成しておきます。

#### Step1: プログラムコードを書いてしまう。
テストケースを作るという言い方もあります。テストプログラムを書いてしまいます。

**<KozaManagerTestクラス>**
```java
public class KozaManagerTest {
/** テスト対象クラス */
private KozaManager target;

/**
* テストの初期化
* 各テスト実行前に起動する
*/
@Before
public void initTest() {
target = new KozaManager();
}

/**
* コンストラクタが起動したかどうかを確認する
* テストケース
*/
@Test
public void testIsInstance() {
assertNotNull(target);
}

/**
* ファイルの存在チェック処理の確認
*/
@Test
public void testIsFile() {
// コンストラクタでファイルが作成されることに注意
assertTrue(target.isFile());
}
}
```

はじめの段階では、**KozaManager**クラスのメソッドはエラーが出るので、中身のないメソッドだけ作ってしまいましょう。
具体的には、下のような形です。
```java
public class KozaManager {
/** 空(から)のコンストラクタ */
public KozaManager() {
}
/** 空のメソッド */
public boolean isFile() {
}
}
```
そして、テストプログラムを動かすことを考えると「コンストラクタで何をしよう?」「isFile()でファイルの有無をどうやって確認する?」と疑問が出ます。筆者が考えたのは、コンストラクタで、ファイルの読み込み準備をしてしまい、isFileでは、ファイルの有無を返すだけにしようと考えました。

<補足>
・ファイルの場所は、「resources/koza.csv」とする。

**<KozaManagerクラス>**
```java
public class KozaManager {
/** ファイルへの書き出しクラス */
private BufferedWriter write;
/** ファイルの読み込みクラス */
private BufferedReader read;
/** 取得(作成)するファイルクラス */
private File file;
/** ファイルのパス */
private static final String FILE_PATH = "resources/koza.csv";

/** コンストラクタ */
public KozaManager() {
// 操作するファイルを指定する
file = new File(FILE_PATH);
try {
write = new BufferedWriter(new FileWriter(file, true));
if (file.exists()) {
read = new BufferedReader(new FileReader(file));
}
} catch (IOException ie) {
ie.printStackTrace();
System.out.println("ファイルオープンに失敗しました。" + ie.getMessage());
System.exit(-1);
}
}

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

/** 作成するファイルが存在するかチェック */
public boolean isFile() {
return file.exists();
}
}
```
処理の順序としては、次のように実装します。
1. コンストラクタで、指定のファイルを読み込み、存在しないときは作成する。
2. Dataクラスに、顧客情報を保存しておき、それをCSVに出力するようにする。

#### Step2: クラスのテスト(起動確認)を行う
テストプログラムの実行を行うだけです。これで、プログラムの実行結果が全部緑になればOKというわけです。
これがOKになったら、次の実装、ファイルhに書き込むとか、データクラスの取得とかそれらを考えて実装、テストの追加を行っていきます。

ここら辺は、慣れが必要なので、やるしかありません。

今日はこの辺で失礼します。でわでわ。。。

<<< 前回 次回 >>>

<Java関連の動画リスト>

<JUnit関連の動画リスト>

でわでわ。。。



JUnit 開発方法〜テスト駆動型開発のススメ〜

イントロダクション

U16プログラミングコンテストのサーバーに接続するクライアントアプリをJavaで実装しようという試みを行っております。

その最中にちょうど、テスト駆動型開発をやることにしたのでメモがてらに記載します。

このプロコンは、こちらにある。旭川発信の大会です。
プログラムの挙動としては、下のようなものです。これを実装するのに行列を使用して、自分、相手プレーヤーの位置を把握するためのプログラムを組もうというところでした。

このプロコンサーバーは、chaiserという名前がついていてQtというフレームワーク(C++)を使用しています。
サーバーにアクセスするためのクライアントプログラムをJavaで作成したのが今回の記事になります。

行列を使用するのに、Pythonで使用しているNumPyを実装しているのがND4jというフレームワークです。
これを使用して、今回の座標から相手と自分の位置を把握する仕組みを作成しようとしました。

テスト駆動型開発?

早い話が「テストを初めに作る」ということなのですが、世間ではどのような認識をしているのか、よくわかっていません。かと言って自分の認識が正しいと思っているわけではないのですが、自分の解釈はこーというものを記載します。

テストを初めに作る

テストを初めに作るということは「仕様を明確にしてやる」ということになります。つまり以下のように仕様を明確にします。

  1. サーバーから受信するデータは1回の操作で3回ある
    ・GetReady: 現在位置を受信
    ・操作コマンド: 操作によるレスポンスを受信
    ・動作終了"#": 現在位置を受信
  2. GetReadyで受信した情報から周囲の確認を行う
    ・ブロック(その場所に移動するとゲームオーバー)の位置
    ・相手(プレーヤ)がいるかどうか
    ・アイテムがあるかどうか
  3. 操作コマンドの受信結果による次の動作の決定
    ・仕様未決定
  4. 動作終了時の処理
    ・これも周囲の確認処理を行う
  5. 操作を行いながら自分の位置を確認するためのMapを作成する
    ・マップのサイズは15 x 17のサイズ
    ・プレーヤーの初期位置はランダムに決定する
    ・使用できるコマンドはSearch, Put, Walk, Lookなど詳細はこちら(U16旭川プログラミングコンテスト)

以上のような仕様で実装します、未決定の部分に関してはこれから考えます。

テスト作成の前に

まずはクラス構成を考えます(詳細設計)。これは規模としては小さいものなので単純にデータを送受信するU16ProconClientクラスとクライアントの細かい操作(マップを作ったり、次の動作を確認したり。。。)を担当するClientManagerクラスの2つを作成します。

U16ProconClientクラスは、実際にサーバーとのやりとりを行いながらやったので割愛します。

ClientManagerの実装

このクラスは、作成する時に上記で決めた仕様を先に実装します。具体的に以下のように実装します。作成したクラスはTestNd4jです。そして、JUnitの作成方法に関してはこちらのページを参照下さい。

まずは仕様を満たすための実装手順をテストケースに書きます。これは仕様の5番目にある部分を実装するために必要な処理なので、上の仕様を満たすための処理のテストケースです。コメントにある「2.移動した時にMapを。。。」とあるぶぶに関しては今後のことを考えて同じようにテスト(実装方法の確認)を行なった次第です。

/**
 * ClientManagerのコンストラクタで、マッピング用行列を初期化する。
 * 1.初期化時に中身を4で埋める
 * 2.移動した時にMapを拡張するので配列の拡張方法も確認
 */
@Test
public void testCreateINDArray() {
    // INT型データの行列を作成する
    INDArray data = Nd4j.create(new int[] {3, 3});
    System.out.println("*** init zeros***");
    System.out.println(data);
    System.out.println("*** putScalar ***");
    System.out.println(data.putScalar(new int[] {2, 1}, 1.0));
    System.out.println("*** init ones ***");
    // 1の値で初期化された配列に全て3を足す
    INDArray reData = Nd4j.ones(new int[] {3, 3}).addi(3);
    System.out.println(reData);
    System.out.println(Nd4j.pad(reData, new int[] {6,  6}, Nd4j.PadMode.CONSTANT));
}

このような形で、仕様→テストケース→実装とやって行くのが自分の認識している「テスト駆動型開発」です。

実装の例(作成中)

/** 自分の周りをチェックするメソッドのテストケース */
@Test
public void testCheckAround() {
    //// 本当はメソッド1つにつき1ケースのテストを行うが、小さなテストなので勘弁してください。。。 ////
    try {
        Method test = this.getPrivateMethod("checkAround", String.class);
        // テストケース1:周囲にアイテムがある場合
        target.setBufMap(new String[] {"0", "0", "0","0", "0", "0", "0", "0", "0"});
        test.invoke(target, "1010010010");
        //(これから実装)テストケース2:周囲に相手プレーヤがいる場合
        //(これから実装)テストケース3:行動できるスペースにブロックがあるかどうか
    } catch (SecurityException e) {
        e.printStackTrace();
        fail("セキュリティ違反です。");
    } catch (InvocationTargetException e) {
        e.printStackTrace();
        fail("メソッド実行時エラーです。");
    } catch (IllegalAccessException e) {
        e.printStackTrace();
        fail("アクセス違反です。");
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
        fail("メソッドの引数違反です。");
    }
}

仕様を先に持ってくるから仕様を詰めて考えたい人(ほとんどの人がそうだと思う)にはうってつけの開発手法だと思います。

Maven利用の場合

ちなみにMavenを使用してJUnitを入れるときは以下のようなコードを追加します。<project>タグの中に入れてください。

  <properties>
   <junit.version>4.12</junit.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>

でわでわ。。。