JUnitでMatcher活用のすすめ

## JUnitでテストを書く

```java
import static org.hamcrest.Matchers.is

assertThat("string", is("string"));
```

この `is()`がMatcherです。
たくさんの種類があるので覚えておくとテストが楽になります

---
## Matcher活用の利点

* コレクション系のassertがシンプルに書ける
* assert失敗時のメッセージがわかりやすくなる
* インスタンスを部分的に検証できる

---
## 使い方

hamcrestのライブラリ
「org.hamcrest:hamcrest-all:1.3」を使います

```xml:pom.xml
<dependency>
  <groupId>org.hamcrest</groupId>
  <artifactId>hamcrest-all</artifactId>
  <version>1.3</version>
  <scope>test</scope>
</dependency>
```

---
## 活かせていない書き方

```java
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を活用した場合

```java
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]>
```

* ループが不要になる
* 失敗メッセージでコレクション全体の内容がわかる

----
## インスタンスの一部を検証

```java
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の一部を紹介

```java
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 が使いやすい

----
## よく困ること

```java
// リストのサイズが3以上 かつ 全てのgetKey() が "k"から始まる
assertThat(/* List<KeyValue> */ keyValueList, 
    allOf(
        hasSize(greaterThanOrEqualTo(3)),
        everyItem(hasProperty("key", startsWith("k")))
    )
);
```

上記はコンパイルできない。

```java
// 修正版
// Matcherが意味する型を明示しないと型推論ができない
assertThat(/* List<KeyValue> */ keyValueList, 
    Matchers.<List<KeyValue>>allOf(
        hasSize(greaterThanOrEqualTo(3)),
        everyItem(hasProperty("key", startsWith("k")))
    )
);
```

----
## 参考

実際のMatcherは以下を参考にしてください

* Matchers (Hamcrest) : Hamcrestの公式javadoc
  * http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matchers.html
* JUnit実践入門 ── 体系的に学ぶユニットテストの技法：書籍案内｜技術評論社
  * http://gihyo.jp/book/2012/978-4-7741-5377-3
* HamcrestのMatchersに定義されているメソッドの使い方メモ - Qiita
  * http://qiita.com/opengl-8080/items/e57dab6e1fa5940850a3
* ご注文は hamcrest-library ですか？　～定義済み Matcher 編～ - 倭マン's BLOG
  * http://waman.hatenablog.com/entry/2015/07/01/082152