JUnitでテストを書く
import static org.hamcrest.Matchers.is
assertThat("string", is("string"));
この is()
がMatcherです。
たくさんの種類があるので覚えておくとテストが楽になります
Matcher活用の利点
- コレクション系のassertがシンプルに書ける
- assert失敗時のメッセージがわかりやすくなる
- インスタンスを部分的に検証できる
使い方
hamcrestのライブラリ
「org.hamcrest:hamcrest-all:1.3」を使います
pom.xml
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
活かせていない書き方
List<String> list =
Arrays.asList("abc", "def", "ghi");
for (String s : list) {
assertThat(s.startsWith("zx"), is(true));
}
# 失敗メッセージ
java.lang.AssertionError:
Expected: is <true>
got: <false>
- Listの要素を自前でループしている
- 失敗メッセージだけでは何がどうダメだったのかわからない
Matcherを活用した場合
List<String> list =
Arrays.asList("abc", "def", "ghi");
assertThat(list, everyItem(startsWith("zx")));
Expected: every item is
a string starting with "zx"
got: <[abc, def, ghi]>
- ループが不要になる
- 失敗メッセージでコレクション全体の内容がわかる
インスタンスの一部を検証
class KeyValue {
private String key;
private String value;
getter / setter ...
// toStringをわかりやすくしておくのがポイント
public String toString() {
return ReflectionToStringBuilder(this);
}
}
KeyValue kv = new KeyValue("k1", "v1");
// 以下は同じ意味
assertThat(kv.getKey(), is("k1"));
assertThat(kv, hasProperty("key", is("k1")));
java.lang.AssertionError:
Expected: hasProperty("key", is "xyz")
got: <key=k, value=v>
- インスタンスの部分的なプロパティだけを検証できる
- 失敗エラーにオブジェクト全体の内容が出力される
Matcherの一部を紹介
List<KeyValue> list = Arrays.asList(
new KeyValue("k1", "v1"),
new KeyValue("k2", "v2"),
new KeyValue("k3", "v3")
);
// hasItems: コレクション内に指定の要素が存在するかどうか
assertThat(list, hasItems(
hasProperty("key", is("k3")),
hasProperty("key", startWith("k2"))
));
// contains: 指定した要素すべてが過不足なく順序通りにマッチすること
assertThat(list, contains(
hasProperty("key", is("k1")),
hasProperty("value", is("v2")),
hasProperty("key", is("k3"))
));
でも残念なことも...
JUnitの最新版 4.11以上 にすると、失敗メッセージがHuman Readableに!
でも、assert対象オブジェクトの内容がわかりにくくなってしまい残念...
java.lang.AssertionError:
Expected: (a collection containing
a string starting with "zx")
but: a collection containing
a string starting with "zx"
was "abc", was "def", was "ghi"
java.lang.AssertionError:
Expected: iterable containing [hasProperty("key", is "k1")]
but: item 1: property 'key' was "k2"
- 個人的には JUnit 4.10 が使いやすい
よく困ること
// リストのサイズが3以上 かつ 全てのgetKey() が "k"から始まる
assertThat(/* List<KeyValue> */ keyValueList,
allOf(
hasSize(greaterThanOrEqualTo(3)),
everyItem(hasProperty("key", startsWith("k")))
)
);
上記はコンパイルできない。
// 修正版
// Matcherが意味する型を明示しないと型推論ができない
assertThat(/* List<KeyValue> */ keyValueList,
Matchers.<List<KeyValue>>allOf(
hasSize(greaterThanOrEqualTo(3)),
everyItem(hasProperty("key", startsWith("k")))
)
);
参考
実際のMatcherは以下を参考にしてください
- Matchers (Hamcrest) : Hamcrestの公式javadoc
- JUnit実践入門 ── 体系的に学ぶユニットテストの技法:書籍案内|技術評論社
- HamcrestのMatchersに定義されているメソッドの使い方メモ - Qiita
- ご注文は hamcrest-library ですか? ~定義済み Matcher 編~ - 倭マン's BLOG