Hamcrest-LibraryのMatchersでassertThatを試してみた

一連のJUnit4.4シリーズもひとまずおしまい.
JUnit4.4にバンドルしているHamcrestのCoreMatchersでは能力不足で,hamcrest-library.jarを加えorg.hamcrest.Matchersを使う事でやっと実用レベルって感じ.
lessThan, greaterThanなどの数値系と,startsWith, endsWithなどの文字列系のMatcherが豊富なのがポイント.
無きゃ作れって言うには基本的過ぎるMatcherだしの.せめて,hamcrest-libraryレベルはJUnitにバンドルしてくれって話だ.


使い方は実際にコードみたほうが分かりやすいだろう.

assertThat(10, is(greaterThan(1)));
assertThat("3.0±0.2の近似値", 3.14, is(closeTo(3, 0.2)));
assertThat("5≦x<10", 5, allOf(greaterThanOrEqualTo(5), lessThan(10)));

assertThat("1, 4, 5のどれか", 4, isOneOf(1, 4, 5));
assertThat("1, 4, 5のどれか", 4, isIn(Arrays.asList(1, 4, 5)));

assertThat("インスタンスから型を検査",
           new ArrayList(), is(List.class));
assertThat("クラスから型を検査",
           ArrayList.class, is(typeCompatibleWith(List.class)));

String str = "The quick brown fox jumps over the lazy dog.";
assertThat("Theで始まり,foxを含み,dog.で終わる文",
           str, allOf(startsWith("The"), containsString("fox"), endsWith("dog.")));
assertThat("Theで始まり,foxかFOXのいずれかを含む文",
           str, allOf(startsWith("The"),
                   anyOf(containsString("fox"), containsString("FOX"))));

↑こんなのが基本.基礎となるMatcherがあってこそ,allOfやanyOfが生きると言うものダ.


あとhasItemArrayとか,おもろいところで,JavaBeans用のMatcherでhasPropertyってヤツ.

String[] array = { "aaa", "bbb", "ccc" };
assertThat(array, hasItemInArray("bbb"));
assertThat(array, hasItemInArray(containsString("aa")));

Hoge hoge = new Hoge("foo", 10);
assertThat(hoge, hasProperty("stringValue"));
assertThat(hoge, hasProperty("stringValue", is("foo")));
assertThat(hoge, hasToString(is(hoge.toString())));
assertThat("hasToStringは何か意味あるのかな?", hoge, is(hoge));

Hogeクラスってのは,こんなどうでもいいヤツ.

public class Hoge {
    private String str;
    private int intValue;
    Hoge(String s, int i) {
        str = s;
        intValue = i;
    }
    public String getStringValue() { return str; }
    public int getIntValue() { return intValue; }
}

んで,assertThatとMatcherの限界感じたのが,次のケース.

Map<String, Integer> map = new HashMap() {
map.put("a", 111);
map.put("b", 222);
map.put("c", 333);
assertThat(map, hasEntry("b", 222));
//assertThat("これはダメ", map, hasKey("b"));
//assertThat("これもダメ", map, hasValue(222));

List<Hoge> list = Arrays.asList(
        new Hoge("aaa.", 5), new Hoge("bbb.", 4), new Hoge("ccc.", 10)
);
//assertThat("これもダメ", list, hasItem(hasProperty("stringValue")));
//assertThat("これもダメ", list, hasItem(<Hoge>hasProperty("stringValue")));


ジェネリクスっつうか型パラメタの推論が式だけでは解決できないので,先のようなのはできそうでできなかった.で,どうすればできるかと言えば,推論できなきゃ明示すりゃいいダケ.
そうゆうとミもフタもないんだけど,その指定がちょっと意外だった.

assertThat("型パラメタを指定する場合,クラス名を省略できないみたい",
           map, Matchers.<String, Integer>hasKey("b"));
assertThat(map, Matchers.<String, Integer>hasValue(222));

//assertThat("これもダメ", list, hasItem(<Hoge>hasProperty("stringValue")));
assertThat("これだとおk",
           list, hasItem(Matchers.<Hoge>hasProperty("stringValue")));

assertThat("当然,allOfやanyOfだと,こうなる",
           list, hasItem(
                     allOf(
                         Matchers.<Hoge>hasProperty("stringValue", is(endsWith("."))),
                         Matchers.<Hoge>hasProperty("intValue", is(greaterThan(3)))
                     )
                 )
          );

せっかくのstaticインポートが台無しなんだけど,まあしゃあないわな.一瞬「こんなシンタックスシュガー分かるか!?」と思ったが,よく考えればJava5の手習い中でも1,2時間で解決できたんだから,いっぱしのJava5使いなら鼻クソみたいな話なんだろな.


ちなみに,expectedとactualを引数で比較するJUnit4.4のassertThatと比べ,自己参照しながらexpectedを組み立てていくFEST-Assertのほうが,型パラメタの伝搬も容易だろと浮気心が湧いたが,あっちはあっちで,allOfやanyOfが無いって欠点があるしな.


さらに,イテレータブルな検査の場合は,JDaveのwhere→Eachの組み合わせのほうが見た目に分かりやすそう...

specify(persons, where(new Each<Person>() {{ matches(item.getSurname(), is("Doe")); }}));
specify(persons, where(new Each<Person>() {{ matches(item.getAge(), is(greaterThan(30))); }}));

と思ってみたりもしたが,どれもこれも一長一短があるし,ヘンに取り合わせたオレ様アサーションを作っても良い事何も無いので,妄想するだけに留めよう.


なんにせよ,Javaでは言語的な限界があるので,あまり文芸的記述に拘るのもどうかと思う.それに,いくら文芸的に記述できたとして,英語でしょ?正直,英文っぽいかどうかはどうでもいいかなって.:-P
とは言え,今までのアサーションよりはassertThatのほうが使い出があるので,JUnitを使うたしなみとして身につけておこう.