Help us understand the problem. What is going on with this article?

JUnitでMatcher活用のすすめ

More than 3 years have passed since last update.

JUnitでMatcher活用のすすめ

by namutaka
1 / 11

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

namutaka
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away