JUnit 実践入門 体系的に学ぶユニットテストの技法 - 第4章 アサーション 値を比較検証するしくみ

第4章 アサーション 値を比較検証するしくみ

4.1 Assert による値の比較検証

  • org.junit.Asset にアサーションメソッドが多数定義されている
    • static インポートされることを想定した static メソッド
  • assertThat と fail 2つ知っていれば十分。

assertThat - 汎用的な値の比較検証

  • JUnit では、比較のルールと比較検証のしくみを分離している
    • ルール: Matcher オブジェクト。場合によってカスタマイズも出来る
    • しくみ: assertThat メソッド。仕組み自体はそのまま使いまわしていける。

fail - テストを失敗させる

4.2 Matcher API によるアサーションの特徴

  • Matcher API は Hamcrest という、JUnit の独立した拡張ライブラリの一つだったが、統合された
  • 以前は assertEquals メソッドに比較ルールを実装していたが、柔軟性や可読性に欠けた
  • 可読性の高い記述
  • 柔軟な比較
  • 詳細な情報の提供

4.3 Matcher API の利用

  • org.hamcrestCoreMatchers クラスや、org.junit.matchers.JUnitMatchers クラスなどに定義された、static なファクトリメソッドから Matchers オブジェクトを生成する。

CoreMatchers が提供する Matcher

  • is
    • 独自実装クラスのオブジェクトに対して利用する場合は、適切な equals メソッドがオーバーライドされているか要確認。基本はデフォルト equals が使われるため、予期しない結果になるかも。
  • nullValue
  • not
  • notNullValue
  • sameInstance
    • == 演算子による同一性比較
    • ボクシング変換(プリミティブ型 -> オブジェクト型)に注意
      assertThat(128, is(sameInstance(128)))
      は失敗する
      • JVM の仕様上128 の Integer オブジェクトが、期待値と実測値のそれぞれで生成されるため
  • instanceOf
    • 実測値が null の場合は必ず失敗

JUnitMatchers が提供する Matcher

  • hasItem
  • hasItems

カスタム Matcher の作成

  • 日付をざっくり比較する(ミリ秒とかいらない) Matcher をつくる

手順

isDate クラスの作成

  • BaseMtcher クラスを extends

    ファクトリメソッド作成

  • static インポートにより、自然言語に近い形で表記したいため
  • プロジェクトで複数のカスタム Matcher 作るなら、ファクトリメソッドをまとめて定義したクラスを作成すると吉

    コンストラクタの定義

    matches メソッドの実装

  • 実測値を引数に取り、一致なら true、そうでなければ false を返す
  • 一致しない場合には、describeTo メソッドが呼び出される

    discribeTo メソッドの実装

  • フレームワークに比較検証が失敗した理由を通知する

    テスト失敗メッセージの確認

  • ここを見て原因がすぐに分かるよう、describeTo を工夫する

ソースコード

プロダクションコード

package ch04;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;

public class IsDate extends BaseMatcher {

    private final int yyyy;
    private int mm;
    private int dd;
    Object actual;

    IsDate(int yyyy, int mm, int dd) {
        this.yyyy = yyyy;
        this.mm = mm;
        this.dd = dd;
    }

    @Override
    public boolean matches(Object actual) {
        this.actual = actual;
        if (!(actual instanceof Date))
            return false;
        Calendar cal = Calendar.getInstance();
        cal.setTime((Date) actual);
        if (yyyy != cal.get(Calendar.YEAR))
            return false;
        if (mm != cal.get(Calendar.MONTH) + 1)
            return false;
        if (yyyy != cal.get(Calendar.DATE))
            return false;
        return true;
    }

    @Override
    public void describeTo(Description desc) {
        desc.appendValue(String.format("%d/%02d/%02d", yyyy, mm, dd));
        if (actual != null) {
            desc.appendText(" but actual is )");
            desc.appendValue(new SimpleDateFormat("yyyy/MM/dd").format((Date) actual));
        }
    }
    
    public static Matcher dateOf(int yyyy, int mm, int dd) {
        return new IsDate(yyyy, mm, dd);
    }
}

テストコード

package ch04;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static ch04.IsDate.*;

import java.util.Date;

import org.junit.Test;

public class IsDateTest {

    @Test
    public void 日付の比較() {
        assertThat(new Date(), is(dateOf(2012, 1, 12)));
}

}

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)