Java Functional Interface〜JUnitのテストでラムダ式にコード短縮〜

ポイント

テストケースの実装中に同じ処理を関数化して実行するように実装しました。

詳細

具体的には以下の赤い部分を2回書かずにFunctional Interfaceを使用するように実装しました。実装したコードはGitにアップしています。参照しているクラスは同じディレクトリに配置してあります。

/** 自分の周りをチェックするメソッドのテストケース */
@Test
public void testCheckAround() {
//// 本当はメソッド1つにつき1ケースのテストを行うが、小さなテストなので勘弁してください。。。 ////
try {
Method test = this.getPrivateMethod("checkAround", String.class);
Consumer func = str -> {

try {
test.invoke(target, str);

} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {

e.printStackTrace();

fail("アクセス違反です。");
           }
       };
//// サンプルデータ1での検証 ////
func.accept("1010010010");
// テストケース1:周囲にアイテムがあるかの判定
ClientData data = target.getClientData();
assertEquals(true, data.isItem());
// テストケース2:周囲に相手プレーヤがいる化の判定
assertEquals(false, data.isPlayer());
// テストケース3:行動できるスペースにブロックがあるかどうか
       WalkHandler handle = data.getHandler();
assertEquals(true, handle.isOkUp());
assertEquals(true, handle.isOkDown());
assertEquals(true, handle.isOkLeft());
assertEquals(true, handle.isOkRight());
//// サンプルデータ2での検証 ////
func.accept("1020020020");
// テストケース1:周囲にアイテムがあるかの判定
ClientData data2 = target.getClientData();
assertEquals(true, data2.isItem());
// テストケース2:周囲に相手プレーヤがいる化の判定
assertEquals(false, data2.isPlayer());
// テストケース3:行動できるスペースにブロックがあるかどうか
WalkHandler handle2 = data2.getHandler();
assertEquals(false, handle2.isOkUp());
assertEquals(false, handle2.isOkDown());
assertEquals(true, handle2.isOkLeft());
assertEquals(true, handle2.isOkRight());
} catch (SecurityException e) {
e.printStackTrace();
fail("セキュリティ違反です。");
} catch (IllegalArgumentException e) {
e.printStackTrace();
fail("メソッドの引数違反です。");
}
}

引数が1つで、返却値がない場合はConsumerを使用する、他の型の場合はそれぞれに対応するIFを使用するようです。参考にしたのはこちらのサイトです。

ちなみに、デバックをしている時の風景をキャプチャしてみました。

でわでわ。。。


## 関連ページ一覧

Eclipse セットアップ

  1. Java Install Eclipse〜開発ツールのインストール〜
  2. TensorFlow C++環境〜EclipseCDTをインストール〜
  3. Setup OpenGL with JavaJOGLを使う準備 for Eclipse
  4. Eclipse Meven 開発手順〜プロジェクトの作成〜
  5. Java OpenCV 環境セットアップ(on Mac)
  6. Eclipse SceneBuilderを追加する
  7. JavaFX SceneBuilder EclipseSceneBuilder連携~

Java Basic一覧

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Git関連

  1. Java Git clone in Eclipse 〜サンプルの取得〜
  2. Eclipse Gitリポジトリの取得 GitからソースをPullしよう〜
  3. IntelliJ IDEA GitGitリポジトリからクローン〜

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>

でわでわ。。。

JUnitクラスの作成サンプル〜@Before,@Afterの使い方〜

JUnitでのテストクラス作成サンプルです。

ポイントになるのはアノテーションで以下のようになっています。

@BeforeClass: テストクラスの実行時に一度だけ起動するメソッド。
<staticメソッドである必要があります、このメソッドはクラスに1つだけ定義されるべきメソッドだからです。>
@AfterClass: テストクラスの終了時に一度だけ起動するメソッド。
<BeforeClassと同様>

@Before: テストメソッドの実行毎に呼ばれるメソッド
@After:  テストメソッドの終了時に呼ばれるメソッド

シンプルにこれだけです。あとはどのようにクラスを実装するか?になります。

サンプルで実装したクラスはこちらにアップロードしてあります。(Git)

/**
 * ND4JのNd4jクラスを学習のため、テストする
 * @author takunoji
 * 2019/05/24
 */
public class TestNd4j {
    /** テスト対象クラス */
    private static ClientManager target;

    /**
     * テストクラスの実行時に一度だけ起動するメソッド。
     * Afterは終了時に呼ばれる
     */
    @BeforeClass
    public static void init() {
         System.out.println("*** BeforeClass ***");
        target = new ClientManager();
    }

    @AfterClass
    public static void after() {
         System.out.println("*** AfterClass ***");
        target = null;
    }

    /**
     * 
     * @param  取得するメソッドの引数の型
     * @param methodName
     * @return
     */
    private  Method getPrivateMethod(String methodName, Class ... args ) {
        Method method = null;
        try {
            if (args == null) {
                method = target.getClass().getDeclaredMethod(methodName);
            } else {
                method = target.getClass().getDeclaredMethod(methodName, String.class);
            }
            // 公開レベルをテストのために変更する
            method.setAccessible(true);
        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
            fail("メソッドの取得に失敗");
        }
        return method;
    }

    /**
     * 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));
    }
   ・
   ・
   ・
   ・

こんな感じです、DBにアクセスしたり、テスト対象クラスが別のクラスを読んでいるときは、Mockしてスタブ実装してやります。そうすることで「単体テスト」を行うことができ、他のクラスを修正したからここのクラスも修正。。。なんていう火炎車になるような事態を避けることができます。

ちなみに、テストクラスなので初めに決めた仕様を満たすための検証を行うクラスなのでちゃんと作成すれば、仕様変更があった時などソースを修正して仕様通りに動けば仕様変更は完了になるので初めに実装しておくとあとあと楽になります。

その他、機能拡張、マイグレーションなどの対応にもテストがあれば(テストケースを通ればOKの状態なら)システムの変更なども作業工数が減り、会社の出費(主に人件費)の削減につながるのではないでしょうか?

でわでわ。。。





ND4J 行列の作成、編集 〜JUNITでのAPIテスト(学習)〜

前提

行列を使用して、マッピングをしたいと思っています。単純に自分のいる場所を中心にして、移動するたびに行列の大きさを変更して行くものです。

ちなみにAPI(Application Interface)は使用するメソッドとかクラスのことです。

ND4Jって?

Deep Learning 4J でも使用する行列計算用のフレームワーク(ライブラリ)です。細かい部分は上記のリンク先に記載してあります。(英語)

兎にも角にもコードを書いてみないと始まらないので書きます。

行列の作成

作成したコードはGitにアップしてあります

初めに3x3の行列を作成する。今後は2x3(例外が出ます)とか数値の部分を変更してやれば良い。

INDArray data = Nd4j.create(new int[] {3, 3});
System.out.println("*** init ***");
System.out.println(data);

指定した部分の値を変更する

System.out.println("*** putScalar ***");
System.out.println(data.putScalar(new int[] {2, 1}, 1.0));

行列のサイズを変更する

INDArray reData = Nd4j.ones(new int[] {3, 3});
System.out.println(reData);
System.out.println(Nd4j.pad(reData, new int[] {1,  1}, Nd4j.PadMode.CONSTANT));

<出力したログ>

*** init zeros***
[[0.00,  0.00,  0.00],  
 [0.00,  0.00,  0.00],  
 [0.00,  0.00,  0.00]]
*** putScalar ***
[[0.00,  0.00,  0.00],  
 [0.00,  0.00,  0.00],  
 [0.00,  1.00,  0.00]]
*** init ones ***
[[1.00,  1.00,  1.00],  
 [1.00,  1.00,  1.00],  
 [1.00,  1.00,  1.00]]
[[0.00,  0.00,  0.00,  0.00,  0.00],  
 [0.00,  1.00,  1.00,  1.00,  0.00],  
 [0.00,  1.00,  1.00,  1.00,  0.00],  
 [0.00,  1.00,  1.00,  1.00,  0.00],  
 [0.00,  0.00,  0.00,  0.00,  0.00]]

追記

1で初期化した後に全ての値に3を足す処理

// 1の値で初期化された配列に全て3を足す
INDArray reData = Nd4j.ones(new int[] {3, 3}).addi(3);
System.out.println(reData);

<出力結果>

[[4.00,  4.00,  4.00],  
 [4.00,  4.00,  4.00],  
 [4.00,  4.00,  4.00]]

でわでわ。。。



Java Basic JUnit 〜テストの作り方〜

イントロダクション

今までに触れたことのない技術(OpenCvなど)を使おうと思ったら色々と動かしてみたいのが人情、そんな時に使えるフレームワークの使い方を記載します。
その名はJUnitです。
具体的には、参考資料、参考サイトなどを見ながらプログラムを作成する、作成したプログラムの。。。

  1. メソッドのみを動かす。
  2. 全体を動かす。
  3. 一部切り出して動かす。

上記のことが、本体プログラムに影響せず実行できるところが良いところです。

JUnit

JUnitは、「テスト用のフレームワーク」として有名なものです。「テスティングフレームワーク」という言い方が多いと思います。

英語で「Testing framework」といえばなんとなくわかるけど、日本語だとなんか別な意味があるのでは?と疑ってしまうのは筆者だけでしょうか?
とりあえず、『いろんな言い方をして混乱するのは良くない』といいたいだけです。失礼。。。

この記事のタイトルにある「テストスイート」という言葉も「テストケース」を意味するものです。JUnitでの「@Test」のついたメソッドのことです。
色々な言い方があり混乱してしまいがちですが「何を指しているのか?」に注意すればあまり混乱しないと思います。

JUnitの概要

JUnit(本家サイトへのリンクです)は、ズバリ「アノテーション『\@』」でテストの準備、テスト、テストの後始末をっコントロールできる便利なフレームワークです。いろんな書籍でいろいろ書いていると思いますが、基本は「\@Test」「/@Before」「\@After」のアノテーションをつければ動きます。もっと言うと「\@Test」アノテーションのみでよいです。※今回実装したJUNIT5ではBeforEach, AfterEachのように名前が変わっています。

JUnitの設定方法

IntelliJ IDEAでの設定方法です。

Mavenを使用して、pom.xmlに以下のような記述を追加、再ビルド(Mavenビルド)を行う。"dependencies"タグの中に追加する
タグの階層があるので、ちょっと混乱しがちですが、projectタグの直下に記述します。

<dependencies>
  <!-- この部分がJUNITの記述 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.7.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

追伸、ほかのOpenCVとかLWJGLなどもこのdependenciesタグの中に追記します。

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd
                                http://maven.apache.org/xsd/settings-1.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>jp.zenryokuservice</groupId>
    <artifactId>TextRPG</artifactId>
    <version>0.8-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>net.objecthunter</groupId>
            <artifactId>exp4j</artifactId>
            <version>0.4.8</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

コードの実行

ズバリ、「@Test」をメソッドの頭につけるだけです。

import jp.zenryoku.practice.sample.SampleLv1Hello;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.Assert.assertEquals;

/**
 * サンプルテストクラス
 */
public class SampleTest {
    /** テスト対象クラス */
    private SampleLv1Hello target;

    /** テストの準備 */
    @BeforeEach
    public void init() {
        target = new SampleLv1Hello();
    }

    @Test
    public void test01() {
        System.out.println("Hello JUnit");
        target.testMethod();
    }

    @Test
    public void test02() {
        target.testMethod(1, 2);
        target.testMethod(4, 9);
        target.testMethod(5,8);
        target.testMethod(6, 7);
    }

    @Test
    public void test03() {
        // 返却値が正しいか確認する
        assertEquals(2, target.testMethod(1, 1, true));
        assertEquals(2, target.testMethod(5, 3, false));
        assertEquals(3, target.testMethod(1, 1, true));
    }
}

Javaで開発をしていたらすぐに目にすると思うのですが、改めて使い方をみてみようと思います。
<作業動画>

手順(Eclipse)

  1. EclipseにJUnit4(JARファイル)をビルドパスにつなぐ
  2. テストクラスを作る
  3. 実行する
  4. あとは色々といじって遊ぶ

サンプルコード(JUnit4 ※古い)

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.junit.runners.JUnit4;
import org.opencv.core.Mat;

import zenryokuservice.gui.lwjgl.kakeibo.opnecv.ReceiptCv;

/** JUnit4を使う宣言 */
@RunWith(JUnit4.class) 
public class ReceiptCvTest {
    /** テスト対象のクラスをフィールドで保持する */
    private ReceiptCv test;
    /**
     * Bforeアノテーションで各テストを実行する前に
     * 実行するメソッド
     * テストのためのデータセットを用意したり、クラスの呼び出しを行なったりする
     */
    @Before
    public void setup() {
        test  = new ReceiptCv();
    }
    @After
    public void terminated() {
        // テストの終了処理(メモリ開放)
        test = null;
    }
    /** テストケース1 */
    @Test
    public void test1() {
        test.helloCv();
    }
    /** テストケース2 */
    @Test
    public void test2() {
        // イメージファイルを読んでみる
        Mat matrix = test.loadImg("download-1.jpg");
        System.out.println(matrix.dump());
    }
}

テストケースの考え方

じゃんけんゲームを作成しているときに、入力チェックのメソッドを作成したとき。

このメソッドをテストするための「テストケース」を作成するときのことを解説しています。

関連ページ一覧

Java Basic

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜