Posted at

JUnitでMatcher活用のすすめ

More than 1 year has passed since last update.


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は以下を参考にしてください