倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

ご注文は hamcrest-library ですか? ~定義済み Matcher 編~

この記事では Hamcrest が提供する JUnit の拡張ライブラリである hamcrest-library を見ていきます。 当初「ScalaTest も『JUnit 実践入門』もまとめて相手してやんよ」シリーズの一環で hamcrest-library を試してたんですが、ちょっと Scala/ScalaTest から離れすぎるので別記事にしました。 見ていく hamcrest-library のバージョンは1.3です。 JUnit にデフォルトで付属されている CoreMatcher に関しては以前の記事でやりましたが、(自分の)復習のためにもう一度見ていきます。waman.hatenablog.com

Matcher の表について
各 Matcher の説明文では、assertThat メソッドの第1引数として渡す検証対象オブジェクトが主語であることを前提にして、主語を省略しています。 また、検証する型に指定されている型は、assertThat の第1引数に指定されるオブジェクトの型です。 これは各ファクトリメソッドが返す Matcher の型パラメータに指定されている型でもあります。 例えば startsWith メソッドは Matcher<String> オブジェクトを返すので、検証する型は String です。 このとき assertThat の第1引数にも String オブジェクトを指定します。 各 Matcher が定義されている型は、JUnit で使えるものは org.hamcrest.CoreMatchers、別途 hamcrest-library への依存性が必要なものは org.hamcrest.Matchers (これは CoreMatchers の Matcher も使える)となっています。 CoreMatchers のみで使える Matcher (のファクトリメソッド)は黒い太字で、Matchers で追加されている Matcher は灰色の太字で書いてます。

下記のサンプルコードでは、is メソッドによる Matcher が必要ない箇所でもあえて is を入れて英文風にしてあります。 この当たりは個人の好みです。 ただし、一部 is を入れないといけない所もありますが、それぞれの箇所で言及しています。

この記事の内容

参考

  • 『JUnit 実践入門』 4.3 Matcher API の使用
  • Java Hamcrest

オブジェクト Object

オブジェクトに関連する Matcher は、全オブジェクトが持つメソッド equals, toString や、null 値、同じ参照かどうか、型の継承関係などの検証を行います。

ファクトリメソッド 検証する型 説明
is(T)
equalTo(T)
T 引数のオブジェクトと Object#equals で等しいことを検証する。
nullValue()
nullValue(Class)
Object
T
null 値であることを検証する。
notNullValue()
notNullValue(Class)
Object
T
null 値でないことを検証する。
sameInstance(T)
theInstance(T)
T 引数のオブジェクトと == によって同じオブジェクトであることを検証する。
instanceOf(Class<?>)
isA(Class<T>)
any(Class<T>)
T 引数のクラスオブジェクトで表される型のインスタンスでことを検証する。
hasToString(String)
hasToString(Matcher<String>)
T toString メソッドで返される文字列が引数の文字列と一致もしくは Matcher とマッチすることを検証する。
typeCompatibleWith(Class<T>) Class<?> 引数のクラスオブジェクトで表される型のサブタイプであることを検証する。
eventFrom(Object)
eventFrom(Class, Object)
EventObject EventObject を発生させたオブジェクト(getSource で返されるオブジェクト)が引数で指定されたオブジェクトであることを検証する。

型パラメータは一部省略しています。 また、CoreMatchers は org.hamcrest パッケージに、HasToString, IsCompatibleType は org.hamcrest.object パッケージに、EventObject は java.util パッケージにあります。

いくつかの Matcher について諸注意。

  • is メソッドから得られる Matcher には equals による検証以外にもコードを英文風に読めるようにする用法もあります。 
  • instanceOf, isA, any は instanceof キーワードによる型検証と同様のことを行います。 検証対象はオブジェクト、Matcher 生成に引数として渡すのは Class オブジェクトです。 一方、typeCompatibleWith は 検証対象も Matcher 生成に引数として渡すのも Class オブジェクトです。 typeCompatibleWith は Class クラスに定義されている isAssignableFrom メソッドと似たような処理ですが、メソッド呼び出し対象と引数が逆転しています。 あえて言うなら isAssignableTo みたいな感じです。
  • hasToString は toString が返す文字列が期待した形式かを検証するメソッドです。
  • eventFrom は JavaBeans というか AWT/Swing のための Matcher ですかね。 JavaFX って EventObject って使ってないっけ?

そう言えば、昔に Class クラスに定義されているメソッドをざっと見た記事を書いたのでそちらも参照のこと:waman.hatenablog.com

これらの Matcher を使ったサンプルコードを見ていきましょう。 not Matcher はまだやってませんが、引数の Matcher の検証が失敗することを検証する Matcher です。

import static java.util.Arrays.asList;

public class ObjectMatcherTest {

    // org.hamcrest.CoreMatchers
    @Test
    public void isもしくはequalToでequalsによる検証を行う(){
        assertThat(asList(1, 2), is(asList(1, 2)));
        assertThat(asList(1, 2), is(not(asList(1L, 2L))));

        assertThat(asList(1, 2), is(equalTo(asList(1, 2))));
        assertThat(asList(1, 2), is(not(equalTo(asList(1L, 2L)))));
    }

    // org.hamcrest.CoreMatchers
    @Test
    public void nullValueでnull値であることnotNullValueでnull値でないことの検証を行う(){
        String actual = getMaybeNull();
        assertThat(actual, is(nullValue()));
        assertThat(actual, is(nullValue(String.class)));

        assertThat("JUnit", is(notNullValue()));
        assertThat("JUnit", is(notNullValue(String.class)));
    }

    String getMaybeNull(){ return null; }

    // org.hamcrest.CoreMatchers
    @Test
    public void sameInstanceもしくはtheInstanceで同じ参照のオブジェクトであることの検証を行う(){
        assertThat("JUnit", is(sameInstance("JUnit")));
        assertThat("JUnit", is(not(sameInstance(new String("JUnit")))));

        assertThat("JUnit", is(theInstance("JUnit")));
        assertThat("JUnit", is(not(theInstance(new String("JUnit")))));
    }

    // org.hamcrest.CoreMatchers
    @Test
    public void instanceOfもしくはisAもしくはanyで型の検証を行う(){
        assertThat("JUnit", is(instanceOf(Serializable.class)));
        assertThat("JUnit", isA(Serializable.class));
        assertThat("JUnit", is(any(Serializable.class)));
    }

    // org.hamcrest.Matchers
    @Test
    public void hasToStringでtoStringの返す文字列を検証する(){
        assertThat(asList("Hello", "world"), hasToString("[Hello, world]"));
        assertThat(Double.parseDouble("1.000"), hasToString("1.0"));
    }

    // org.hamcrest.Matchers
    @Test
    public void typeCompatibleWithで指定したクラスがスーパータイプであることを検証する(){
        assertThat(ArrayList.class, is(typeCompatibleWith(List.class)));
        assert(List.class.isAssignableFrom(ArrayList.class));    // typeCompatibleWith と逆

        assertThat(List.class, is(not(typeCompatibleWith(ArrayList.class))));
            // 当然逆にするとダメ
    }

    // org.hamcrest.Matchers
    @Test
    public void eventFromで(){
        // Setup
        List<java.awt.event.ActionEvent> eventStack = new LinkedList<>();

        JButton button = new JButton("テスト");
        button.addActionListener(eventStack::add);

        // Exercise
        button.doClick();

        // Verify
        assertThat(eventStack.get(0), is(eventFrom(button)));
    }
}

オブジェクトの等しさを検証するにあたって、Object#equals による検証の方が演算子 == による検証よりも簡単にできるようになってます。 == による、同じ参照のオブジェクトであることの検証は isInstance, theInstance のように「Instance」というキーワードが付いてます(instanceOf は型の検証をしますが)。

文字列 String

文字列に関連する Matcher は、特定の文字列で始まったり終わったりと言った、文字列の内容を検証します。 検証する型はすべて String です。

ファクトリメソッド 説明
startsWith(String) 指定された文字列で始まることを検証する。
containsString(String) 指定された文字列を含むことを検証する。
endsWith(String) 指定された文字列で終わることを検証する。
isEmptyOrNullString() 空文字列もしくは null 値であることを検証する。
isEmptyString() 空文字列であることを検証する。
equalToIgnoringWhiteSpace(String) 空白文字を除いて指定された文字列と一致することを検証する。
equalToIgnoringCase(String) 大文字小文字を同一視して指定された文字列に一致することを検証する。
stringContainsInOrder(Iterable<String>) 指定された文字列を順に含むことを検証する。

CoreMatchers は org.hamcrest パッケージに、IsEmptyString, IsEqualIgnoringWhiteSpace, IsEqualIgnoringCase, StringContainsInOrder クラスは org.hamcrest.text パッケージにあります。

equalToIgnoringWhiteSpace が生成する Matcher では、検証対象の文字列と引数の文字列について

  • 先頭と末尾の空白文字は削除
  • 途中の空白文字もしくはその連なりは1つのスペースに変換

という処理を施した後に文字列として等しいかどうかを検証します。 どの文字が空白文字なのかは JavaDoc には明示されてないので、Character#isWhitespace が true を返す文字とかなのかな? 半角スペース以外に \r, \n, \t などは空白文字として認識されるのは試しました。 あまり参考にならないかも知れませんが、Java での空白文字についてはwaman.hatenablog.com
を参考のこと。

ではこれらの Matcher を使ったサンプルコード:

public class StringMatcherTest {

    final String string = "Hello JUnit world";
    final String nullString = null;
    final String emptyString = "";

    // org.hamcrest.CoreMatchers
    @Test
    public void startsWithで特定の文字列から始まることを検証する(){
        assertThat("Hello JUnit world", startsWith("Hello"));
    }

    // org.hamcrest.CoreMatchers
    @Test
    public void containsStringで特定の文字列が含まれることを検証する() {
        assertThat("Hello JUnit world", containsString("JUnit"));
    }

    // org.hamcrest.CoreMatchers
    @Test
    public void endsWithで特定の文字列で終わることを検証する(){
        assertThat("Hello JUnit world", endsWith("world"));
    }

    // org.hamcrest.Matchers
    @Test
    public void isEmptyOrNullStringでnull値もしくは空文字であることを検証する(){
        assertThat(nullString, isEmptyOrNullString());
        assertThat(emptyString, isEmptyOrNullString());
        assertThat(string, not(isEmptyOrNullString()));
    }

    // org.hamcrest.Matchers
    @Test
    public void isEmptyStringでだ空文字であることを検証する(){
        assertThat(nullString, not(isEmptyString()));
        assertThat(emptyString, isEmptyString());
        assertThat(string, not(isEmptyString()));
    }

    // org.hamcrest.Matchers
    @Test
    public void equalToIgnoringWhiteSpaceで空白文字を除いて一致するか検証する(){
        assertThat("Hello JUnit world", is(equalToIgnoringWhiteSpace("Hello\tJUnit\t\r\nworld")));
    }

    // org.hamcrest.Matchers
    @Test
    public void equalToIgnoringCaseで大文字小文字を除いて一致するか検証する(){
        assertThat("Hello JUnit world", is(equalToIgnoringCase("hello junit world")));
    }

    // org.hamcrest.Matchers
    @Test
    public void stringContainsInOrderで指定された複数の文字列が順に含まれているか検証する(){
        assertThat("Hello JUnit world", stringContainsInOrder(asList("Hello", "world")));
        assertThat("Hello JUnit world", not(stringContainsInOrder(asList("world", "Hello"))));
    }
}

数値 Number

数値に関連する Matcher は、主に数値の大小関係を検証します。

ファクトリメソッド 検証する型 説明
comparesEqualTo(T) T Comparable#compareTo で値が等しいことを検証する。
lessThan(T) T 引数の数値より小さいことを検証する。
lessThanOrEqualTo(T) T 引数の数値以下であることを検証する。
greaterThan(T) T 引数の数値より大きいことを検証する。
greaterThanOrEqualTo(T) T 引数の数値以上であることを検証する。
closeTo(double, double) Double 第1引数で指定された double 値に対する誤差が第2引数で与えられた double 値以下であることを検証する。
closeTo(BigDecimal, BigDecimal) BigDecimal 第1引数で指定された BigDecimal 値に対する誤差が第2引数で与えられた BigDecimal 値以下であることを検証する。

T は Comparable<T> を拡張する型を表す型パラメータです。 また、定義されているクラスは org.hamcrest.number パッケージにあります。

closeTo(double operand, double error) が生成する Matcher は、検証対象の数値 n が

  { \displaystyle
\begin{align*}
  \left|n - \textrm{operand}\right| \leqq \textrm{error}
\end{align*}
}

を満たす、もしくは同じことだけど n が

  { \displaystyle
\begin{align*}
 \textrm{operand} - \textrm{error} \leqq n \leqq \textrm{operand} + \textrm{error}
\end{align*}
}

の範囲にあることを検証します。 ただし、double 値では == があまり正確ではないので、値が境界値に乗る場合をあまり厳密に検証しない方がよいでしょう。 BigDecimal を検証する closeTo は正確に検証しますが、BigDecimal オブジェクトを取得する際に double 値による不正確さが混入すると台無し。 このあたりの話は Matcher には関係ないけど。

ではサンプルコード。 数値間の大小関係を <, > などの数学演算子ではなく、lessThan, greaterThan などの英文風になってるので、日本人的には分かりにくくなってる感じがしないでもないですが、やってることはどうってことないことです:

public class NumberMatcherTest {

    // org.hamcrest.Matchers
    @Test
    public void comparesEqualToで値が等しいことを検証する(){
        assertThat(0, not(comparesEqualTo(-1)));
        assertThat(0, comparesEqualTo(0));
        assertThat(0, not(comparesEqualTo(1)));
    }

    // org.hamcrest.Matchers
    @Test
    public void lessThanで指定された値より小さいことを検証する(){
        assertThat(0, is(not(lessThan(-1))));
        assertThat(0, is(not(lessThan(0))));
        assertThat(0, is(lessThan(1)));
    }

    // org.hamcrest.Matchers
    @Test
    public void lessThanOrEqualToで指定された値より小さいか等しいことを検証する(){
        assertThat(0, is(not(lessThanOrEqualTo(-1))));
        assertThat(0, is(lessThanOrEqualTo(0)));
        assertThat(0, is(lessThanOrEqualTo(1)));
    }

    // org.hamcrest.Matchers
    @Test
    public void greaterThanで指定された値より大きいことを検証する(){
        assertThat(0, is(greaterThan(-1)));
        assertThat(0, is(not(greaterThan(0))));
        assertThat(0, is(not(greaterThan(1))));
    }

    // org.hamcrest.Matchers
    @Test
    public void greaterThanOrEqualToで指定された値より大きいか等しいことを検証する(){
        assertThat(0, is(greaterThanOrEqualTo(-1)));
        assertThat(0, is(greaterThanOrEqualTo(0)));
        assertThat(0, is(not(greaterThanOrEqualTo(1))));
    }

    // org.hamcrest.Matchers
    @Test
    public void closeToで指定した範囲内で浮動小数点数が一致することを検証する(){
        assertThat(0.98, is(not(closeTo(1.0, 0.015))));
        assertThat(0.99, is(closeTo(1.0, 0.015)));
        assertThat(1.00, is(closeTo(1.0, 0.015)));
        assertThat(1.01, is(closeTo(1.0, 0.015)));
        assertThat(1.02, is(not(closeTo(1.0, 0.015))));
    }

    // org.hamcrest.Matchers
    @Test
    public void closeToで指定した範囲内でBigDecimal値が一致することを検証する(){
        assertThat(big("0.98"), is(not(closeTo(big("1.0"), big("0.01")))));
        assertThat(big("0.99"), is(closeTo(big("1.0"), big("0.01"))));
        assertThat(big("1.00"), is(closeTo(big("1.0"), big("0.01"))));
        assertThat(big("1.01"), is(closeTo(big("1.0"), big("0.01"))));
        assertThat(big("1.02"), is(not(closeTo(big("1.0"), big("0.01")))));
    }

    static BigDecimal big(String d){ return new BigDecimal(d); }
}

最後の BigDecimal の範囲を検証するテストでは、double 値の誤差をなくすために文字列から BigDecimal 値をインスタンス化しています。

コレクション Collection

次はコレクションに関連する Matcher を見ていきます。 これらの Matcher は org.hamcrest.collection パッケージにあります。 このパッケージ内には Collection、配列、Map に関連する Matcher がまとめて置かれていますが、ここでは別々に見ていきます。

コレクション Collection
まずは Collection に関連する Matcher。 Iterable に関連するものも見ていきます。 Collection の Matcher は Iterable のものとほとんど同じですが、おそらく効率的な実装をするために別途作ってるのではないでしょうか。 名前的には Collection 関連のものの方がわかりやすいです(hasSize と iterableWithSize など)。 以下の表で、要素の型を表す型パラメータは E とします。

ファクトリメソッド 検証する型 説明
hasItem(E)
hasItem(Matcher<E>)
Iterable<E> 指定されたオブジェクトに等しいオブジェクト、もしくは Matcher にマッチするオブジェクトを含むことを検証する。
hasItems(E...)
hasItems(Matcher<E>...)
Iterable<E> 指定されたオブジェクトに等しいオブジェクト、もしくは Matcher にマッチするオブジェクトすべてを含むことを検証する。
everyItem(Matcher<E>) Iterable<E> すべての要素が引数の Matcher にマッチすることを検証する。
isIn(Collection<E>)
isIn(E[])
isOneOf(E...)
E 引数の Collection または配列に要素として含まれていることを検証する。
empty()
emptyCollectionOf(Class<E>)
Collection<E> 空集合であることを検証する。
hasSize(int)
hasSize(Matcher<Integer>)
Collection<E> 要素数が引数で指定された値に等しいか、引数の Matcher にマッチすることを検証する。
emptyIterable()
emptyIterableOf(Class<E>)
Iterable<E> 空の Iterable であることを検証する。
iterableWithSize(int)
iterableWithSize(
Matcher<Integer>)
Itrable<E> 要素数が引数で指定された値、もしくは引数で指定された Matcher にマッチすることを検証する。
contains(E...)
contains(List<Matcher<E>>)
contains(Matcher<E>)
contains(Matcher<E>...)
Iterable<E> 引数のコレクションまたは配列の要素に等しいオブジェクト、もしくは Matcher にマッチするオブジェクトを順に含むことを検証する。 検証対象の Iterable のサイズと引数の List もしくは配列のサイズは一致する必要がある。
containsInAnyOrder(E...)
containsInAnyOrder(
Collection<Matcher<E>>)
containsInAnyOrder(
Matcher<E>...)
Iterable<E> 引数のコレクションまたは配列の要素に等しいオブジェクト、もしくは Matcher にマッチするオブジェクトを順序を問わず含むことを検証する。 検証対象の Iterable のサイズと引数の Collection もしくは配列のサイズは一致する必要がある。

型パラメータは一部省略しているものがあります。

  • isIn(E[]) と isOneOf(E...) は同じ振る舞いをしますが、引数が配列か可変長引数かの違いです(というか isOneOf の JavaDoc に isIn と書かれとるんだが・・・)。
  • contains, containsInAnyOrder は検証対象の Iterable と引数の Collection もしくは配列のサイズが等しくなければなりません。 つまり、引数で指定されていないオブジェクトが検証対象に含まれていても検証が失敗します。 このあたりは文字列関連の Matcher である stringContainsInOrder とは異なる挙動なので注意が必要。
  • contains は順序を気にするため、引数として Collection を渡す場合は List を渡します。 一方、containsInAnyOrder は順序を気にしないので、引数としてすべての Collection を、特に Set も受け取ることができます。
public class CollectionMatcherTest {

    final List<String> list = asList("Hello", "JUnit", "world");
    final List<String> emptyList = Collections.emptyList();

    //***** Core Matchers *****

    // org.hamcrest.CoreMatchers
    @Test
    public void hasItemで特定の要素を含むことを検証する(){
        assertThat(list, hasItem("JUnit"));
        assertThat(list, not(hasItem("ScalaTest")));
    }

    // org.hamcrest.CoreMatchers
    @Test
    public void hasItemsで指定された複数の要素すべてを含むことを検証する(){
        assertThat(list, hasItems("Hello", "world"));
        assertThat(list, not(hasItems("Hi", "world")));
    }

    // org.hamcrest.CoreMatchers
    @Test
    public void everyItemですべて指定された条件満たすことを検証する(){
        assertThat(list, everyItem(hasLength(4)));
    }

    // 文字列の長さが引数の値であることを検証する Matcher を返す
    static Matcher<String> hasLength(int n){
        return new TypeSafeMatcher<String>() {

            @Override
            protected boolean matchesSafely(String s){
                return s.length() == n;
            }

            @Override
            public void describeTo(Description description) {
                description.appendValue("Length of String is " + n);
            }
        };
    }

    //***** Collection Matchers *****

    // org.hamcrest.Matchers
    @Test
    public void isInもしくはisOneOfで引数のコレクションもしくは配列に含まれていることを検証する(){
        assertThat("JUnit", isIn(asList("Hello", "JUnit", "world")));
        assertThat("JUnit", isIn(new String[]{"Hello", "JUnit", "world"}));
        assertThat("JUnit", isOneOf("Hello", "JUnit", "world"));
    }

    // org.hamcrest.Matchers
    @Test
    public void emptyとemptyCollectionOfでコレクションが空であることを検証する(){
        assertThat(emptyList, is(empty()));
        assertThat(emptyList, is(emptyCollectionOf(String.class)));
    }

    // org.hamcrest.Matchers
    @Test
    public void hasSizeで要素数を検証する(){
        assertThat(emptyList, hasSize(0));
        assertThat(list, hasSize(3));
        assertThat(list, hasSize(lessThan(10)));
    }

    //***** Iterable Matchers *****

    final Iterable<String> emptyIte = Collections.emptySet();
    final Iterable<String> ite = new HashSet<>(list);

    // org.hamcrest.Matchers
    @Test
    public void emptyIterableとemptyIterableOfで空であることを検証する(){
        assertThat(emptyIte, is(emptyIterable()));
        assertThat(emptyIte, is(emptyIterableOf(String.class)));
    }

    // org.hamcrest.Matchers
    @Test
    public void iterableWithSizeで要素数を検証する(){
        assertThat(emptyIte, is(iterableWithSize(0)));
        assertThat(ite, is(iterableWithSize(3)));
        assertThat(ite, is(iterableWithSize(lessThan(10))));
    }

    // org.hamcrest.collection.IsCollectionContainingInOrder
    @Test
    public void containsで要素が指定された順序で含まれていることを検証する(){
        assertThat(list, contains("Hello", "JUnit", "world"));
        assertThat(list, not(contains("Hello", "JUnit")));
    }

    // org.hamcrest.Matchers
    @Test
    public void containsInAnyOrderで要素が順序を問わず含まれているを検証する(){
        assertThat(ite, containsInAnyOrder("Hello", "world", "JUnit"));
        assertThat(ite, not(containsInAnyOrder("world", "JUnit")));
    }
}

配列 Array
配列関連の Matcher は配列のサイズや含まれている要素についての検証を行います。 基本的には Collection / Iterable 関連の Matcher を配列版に焼き直したものです。 検証する型はすべて配列なので省略。 配列の要素の型は E とします。

ファクトリメソッド 説明
emptyArray() 空配列であることを検証する。
arrayWithSize(int)
arrayWithSize(Matcher<Integer>)
サイズが指定された値に等しい、もしくは Matcher にマッチすることを検証する。
array(Matcher<? super T>...) 要素が指定された Matcher の配列に順にマッチすることを検証する。
hasItemInArray(E)
hasItemInArray(Matcher<T>)
指定されたオブジェクトに等しい、もしくは Matcher にマッチするオブジェクトを含むことを検証する。
arrayContaining(E...)
arrayContaining(Matcher<E>...)
arrayContaining(List<Matcher<E>>)
要素が順に指定された配列もしくは List の要素に等しい、もしくはマッチすることを検証する。 検証対象の配列のサイズは引数の配列もしくは List のサイズに等しくなければいけない。
arrayContainingInAnyOrder(E...)
arrayContainingInAnyOrder(
Matcher<E>...)
arrayContainingInAnyOrder(
Collection<Matcher<E>>)
要素が順序を問わず指定された配列または Collection の要素に等しい、もしくはマッチすることを検証する。 検証対象の配列のサイズは引数の配列もしくは Collection のサイズに等しくなければならない。

型パラメータを一部省略しているところがあります。

  • array の引数は Matcher の配列(可変長引数)なので、等しくあってほしいオブジェクトを書くときでも is や equalTo などの Matcher を指定する必要があります。 オブジェクトをそのまま書きたい場合は arrayContaining を使えます。
  • hasItemInArray は引数が1つの arrayContaining, arrayContainingInAnyOrder と同じです。 おそらく効率的な実装のために別途定義されているのだと思います。

ではサンプルコード。 Colleciton / Iterable 関連の Matcher と大して変わらず:

public class ArrayMatcherTest {

    final String[] anArray = new String[]{"one", "two", "three"};
    final Integer[] intArray = new Integer[]{1, 2, 3};
    final String[] emptyArray = new String[]{};

    // org.hamcrest.Matchers
    @Test
    public void arrayWithSizeとemptyArrayで配列のサイズが指定した値であることを検証する() {
        assertThat(anArray, is(arrayWithSize(3)));
        assertThat(intArray, is(arrayWithSize(lessThan(10))));    // Integer の Matcher を使える
        assertThat(emptyArray, is(emptyArray()));
    }

    // org.hamcrest.Matchers
    @Test
    public void arrayで各要素がMatcherにマッチすることを検証する() {
        assertThat(anArray, is(array(is("one"), is("two"), is("three"))));    // 引数は Matcher
        assertThat(intArray, is(array(equalTo(1), equalTo(2), equalTo(3))));
    }

    // org.hamcrest.Matchers
    @Test
    public void hasItemInArrayで配列が指定した要素を持つことを検証する() {
        assertThat(anArray, hasItemInArray("one"));
        assertThat(anArray, hasItemInArray(startsWith("th")));
    }

    // org.hamcrest.Matchers
    @Test
    public void arrayContainingで指定した要素を順に含むことを検証する() {
        assertThat(anArray, arrayContaining("one", "two", "three"));
        assertThat(anArray, not(arrayContaining("one", "two")));
    }

    // org.hamcrest.Matchers
    @Test
    public void arrayContainingInAnyOrderで順序を問わず指定した要素を含むことを検証する() {
        assertThat(anArray, arrayContainingInAnyOrder("two", "one", "three"));
        assertThat(anArray, not(arrayContainingInAnyOrder("one", "two")));
    }
}

マップ Map
Map 関連の Matcher は Map のエントリやキー、値が存在することを検証します。

ファクトリメソッド
《定義されているクラス》
検証する型 説明
hasEntry(K, V)
hasEntry(Matcher<? super K>, Matcher<? super V>)
Map<? extends K,? extends V> 指定したキーと値のエントリがあることを検証する。
hasKey(K)
hasKey(Matcher<? super K>)
Map<? extends K,?> 指定したキーがあることを検証する。
hasValue(V)
hasValue(Matcher<? super V>)
Map<?,? extends V> 指定した値があることを検証する。

K, V はそれぞれ Map のキーと値の型パラメータです。 また IsMapContaining クラスは org.hamcrest.collection パッケージにあります。

サンプルコード。 特に使い方が難しいものはありません。 検証対象の Map を作るのが面倒だなぁ、Java さん。

public class MapMatcherTest {

    final Map<String, Integer> map = new HashMap<>();

    @Before
    public void mapを初期化(){
        map.put("one", 1);
        map.put("two", 2);
        map.put("three", 3);
    }

    // org.hamcrest.Matchers
    @Test
    public void hasEntryで指定したエントリを持つことを検証する(){
        assertThat(map, hasEntry("one", 1));
        assertThat(map, not(hasEntry("two", 3)));
    }

    // org.hamcrest.Matchers
    @Test
    public void hasKeyで指定したキーを持つことを検証する(){
        assertThat(map, hasKey("two"));
        assertThat(map, not(hasKey("four")));

        assertThat(map, hasKey(startsWith("t")));    // Matcher も使える
    }

    // org.hamcrest.Matchers
    @Test
    public void hasValueで指定した値を持つことをを検証する(){
        assertThat(map, hasValue(3));
        assertThat(map, not(hasValue(4)));
    }
}

JavaBeans

JavaBeans 関連の Matcher は JavaBeans のプロパティ(getter メソッド)が存在することやその値の検証を行います。 検証される型は hasProperty, hasPropertyWithValue は(実質的に)Object *1、samePropertyValueAs は引数の型と同じです。

ファクトリメソッド 説明
hasProperty(String) 指定された JavaBeans プロパティが存在することを検証する。
hasProperty(String, Matcher<?>) 第1引数で指定された JavaBeans プロパティが第2引数で指定された Matcher にマッチすることを検証する。
samePropertyValuesAs(T) 第1引数で指定された XPath に合致するノードの値が第2引数指定された値にマッチすることを検証する。

これらのメソッドが定義されているクラスは org.hamcrest.beans パッケージにあります。

さて、サンプルコードを見ていく前にまず準備。 次のような JavaBeans クラスがあったとしましょう(本当は JavaBeans には引数なしのコンストラクタが必要ですが、ここで扱う Matcher には getter メソッドがあれば充分です):

public class Person{

    private final String name;
    private final int age;
    private final boolean isMarried;

    public Person(String name, int age, boolean isMarried) {
        this.name = name;
        this.age = age;
        this.isMarried = isMarried;
    }

    public String getName() { return name; }

    public int getAge() { return age; }

    public boolean isMarried() { return isMarried; }
}

boolean 値のプロパティでは get でなく is で始まっても OK です。 この Person クラスを用いて、テストコードはこんな感じになります:

public class BeanMatcherTest {

    final Person me = new Person("waman", 100, false);
    final Person anotherMe = new Person("waman", 100, false);  // me と同じプロパティ
    final Person you = new Person("waman", 50, true);  // me と一部異なるプロパティ

    // org.hamcrest.Matchers
    @Test
    public void hasPropertyで指定されたプロパティが存在することを検証する(){
        assertThat(me, hasProperty("name"));
        assertThat(me, hasProperty("age"));
        assertThat(me, hasProperty("married"));
    }

    // org.hamcrest.Matchers
    @Test
    public void hasPropertyで指定されたプロパティが指定された値を持つことを検証する(){
        assertThat(me, hasProperty("name", is("waman")));
        assertThat(me, hasProperty("age", is(100)));
        assertThat(me, hasProperty("married", is(false)));
    }

    // org.hamcrest.Matchers
    @Test
    public void samePropertyValueAsで指定されたオブジェクトとプロパティがすべて同じことを検証する(){
        assertThat(anotherMe, is(not(sameInstance(me))));  // 別インスタンスであることを確認
        assertThat(anotherMe, is(samePropertyValuesAs(me)));
        assertThat(you, is(not(samePropertyValuesAs(me))));
    }
}

2つ目の hasProperty は第2引数が Matcher なので、is(...) や equal(...) などと書く必要があります。 samePropertyValuesAs はすべてのプロパティが一致している場合に限りマッチします(まぁ、そりゃそうだろう)。

XML

XML 関連の Matcher は、DOM ノードに対して指定した XPath にマッチするノードが存在することを検証します。 検証する型はどちらも org.w3c.dom.Node なので省略

ファクトリメソッド 説明
hasXPath(String)
hasXPath(String, NamespaceContext)
指定された XPath にマッチするノードが存在することを検証する。
hasXPath(String, Matcher<String>)
hasXPath(String, NamespaceContext, Matcher<String>)
第1引数で指定された XPath に合致するノードの値が第2引数指定された値にマッチすることを検証する。

HasXPath クラスは org.hamcrest.xml パッケージにあります。

これらを用いたテストコードはこんな感じになります:

public class XmlMatcherTest {

    Node xml;

    @Before
    public void xmlを初期化()throws Exception{
        String xmlSource = "<document><node/><node name='waman'/></document>";
        InputSource source = new InputSource();
        source.setCharacterStream(new StringReader(xmlSource));

        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        xml = builder.parse(source);
    }

    // org.hamcrest.Matchers
    @Test
    public void hasXPathで指定したXPathに対応するノードがあることを検証する(){
        assertThat(xml, hasXPath("/document/node[1]"));
        assertThat(xml, not(hasXPath("/document/node[3]")));
    }

    // org.hamcrest.Matchers
    @Test
    public void hasXPathで指定したXPathに対応するノードが指定したMatcherにマッチする値を持つことを検証する(){
        assertThat(xml, hasXPath("/document/node[2]/@name", is("waman")));
    }
}

値を検証する hasXPath は、値の指定に Matcher を指定する必要があります。 久し振りに DOM を扱うコード書いたけど、DocumentBuilderFactory とか完全に忘れてたヨ。 テスト用の DOM ノード用意する方に手間がかかる。

論理演算
ジェネリクスの型パラメータ部分は少々省略しています。

ファクトリメソッド 検証する型 説明
anything() Object 常に成功する検証をする。
not(T)
not(Matcher<T>)
T 引数の値が等しくない、もしくは Matcher がマッチしないことを検証する。
allOf(Iterable<Matcher<T>>)
allOf(Matcher<T>...)
both(Matcher<LHS>)
T 引数の Matcher のすべてマッチすることを検証する(both についてはサンプルコード参照)。
anyOf(Iterable<Matcher<T>>)
anyOf(Matcher<T>...)
either(Matcher<LHS>)
T 引数の Matcher のいずれかがマッチすることを検証する(either についてはサンプルコード参照)。

これらはすべて org.hamcrest.CoreMatchers に定義されています。

ではサンプルコード。 both と either はそれ自体を assertThat の第2引数にするのではなく、それらのオブジェクトに対して and や or メソッドを呼び出して、その返り値の Matcher を assertThat に渡します。 allOf, anyOf とどちらが使いやすいか(見やすいか)は人それぞれかと:

public class LogicalMatcherTest {

    final Map<String, Integer> map = new HashMap<>();

    @Before
    public void mapを初期化(){
        map.put("one", 1);
        map.put("two", 2);
        map.put("three", 3);
    }

    // org.hamcrest.CoreMatchers
    @Test
    public void anythingはSUTに関係なくマッチすることを検証する(){
        assertThat("JUnit", is(anything()));
    }

    // org.hamcrest.CoreMatchers
    @Test
    public void notで否定を検証する(){
        assertThat("JUnit", is(not("ScalaTest")));
    }

    // org.hamcrest.CoreMatchers
    @Test
    public void allOfまたはbothで複数の条件を同時に満たすことを検証する(){
        assertThat("JUnit", is(allOf(
                startsWith("J"),
                containsString("Uni"),
                endsWith("Unit"))));

        assertThat("JUnit", is(
                both(startsWith("J"))
                        .and(containsString("Uni"))
                        .and(endsWith("Unit"))
        ));

        assertThat(map, is(
                both(hasKey("one")).and(not(hasValue(7)))
        ));
    }

    // org.hamcrest.CoreMatchers
    @Test
    public void anyOfもしくはeitherで複数の条件のどれかを満たすことを検証する(){
        assertThat("JUnit", anyOf(
                                is("JUnit"),
                                is("ScalaTest"),
                                is("Spock")));

        assertThat("JUnit", either(is("JUnit"))
                                .or(is("ScalaTest"))
                                .or(is("Spock")));
    }
}

まぁ、条件が複雑なのでコードがちょっと複雑になっても仕方ないかな。

以上で hamcrest-library (1.3) に定義されている Matcher を見てきましたが、JUnit でデフォルトで使える CoreMatchers の Matcher に加えて便利そうな Matcher がいろいろ定義されてますね。 ただ、これらを見てると文字列で正規表現を使いたいとかサイズの異なる Collection を比べたいとかいった欲も出てきますな。 あと、Java8 で導入された Stream API の Matcher とか。 まぁ、これらはおそらく探せばそういうライブラリが見つかるかと思いますが、結構自分で独自の Matcher を定義するのもそんなに難しくないので(Collection のところでちょっとやってみましたが)、これに関する記事も書く予定。 そんなに長くならないと思います。github.com

追記
CoreMatchers にない Matcher は Matchers クラスの static ファクトリメソッドで生成できるので、表中の「定義されているクラス」を削除して、メソッドの色を変えました。

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

*1:これらのファクトリメソッドはジェネリックなメソッドとして定義されていますが、その型パラメータがメソッドのシグニチャのどこにも使われていないので、実質的に Object を指定しているのと同じようなものだと思うのですが・・・