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>

でわでわ。。。

投稿者:

takunoji

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

コメントを残す