Java好き

カテゴリ: JUnit

このページでは
Matchers(CoreMatchers)のCollection(Iterableも含む)とMapの検証について
注意点と使い方をまとめる。

この他のMatcherの検証については、こちらのページを参照。
(このサイト内の別記事)

Iterableの検証
検証内容 メソッド Matchers CoreMatchers
空のIterableであるか emptyIterable()
指定した型がバインドされた空のIterableであるか emptyIterableOf()
すべての要素にマッチするか everyItem()
指定した要素が一つでも一致する(マッチする)か hasItem()
指定した複数の各要素が一つでも一致する(マッチする)か hasItems()
各要素がすべて一致(マッチする)するか contains()
順序に関係なく各要素がすべて一致(マッチする)するか containsInAnyOrder()
Collectionの検証
検証内容 メソッド Matchers CoreMatchers
空のCollectionであるか empty()
指定した型を要素に持つ空のCollectionであるか emptyCollectionOf()
指定したサイズであるか hasSize()
Mapの検証
検証内容 メソッド Matchers CoreMatchers
指定したキーを持っているか hasKey()
指定した値を持っているか hasValue()
指定したエントリーを持っているか hasEntry()

Iterableの検証

空のIterableであるか - emptyIterable()

List<String> actual = Collections.emptyList();
assertThat(actual, is(emptyIterable()));

指定した型がバインドされた空のIterableであるか - emptyIterableOf()

emptyIterable()に加えて、
バインドされた型も検証する

List<String> actual = Collections.emptyList();
assertThat(actual, is(emptyIterableOf(String.class)));

すべての要素にマッチするか - everyItem()

引数で指定したMatcherを各要素で評価して
すべての要素でtrueになれば検証OK。

List<String> actual = Arrays.asList("Hello", "World");
assertThat(actual, everyItem(containsString("o")));

指定した要素が一つでも一致する(マッチする)か - hasItem()

指定した要素が一つでも一致したら、
検証OK。

List<String> actual = Arrays.asList("Hello", "World", "!!");
assertThat(actual, hasItem("World"));

指定した複数の各要素が一つでも一致する(マッチする)か - hasItems()

hasItem()の複数指定可能版。

List<String> actual = Arrays.asList("Hello", "World", "!!");
assertThat(actual, hasItems("Hello", "!!"));

各要素がすべて一致(マッチする)するか - contains()

順序も含めた完全一致。

containsには含めるの意味があるが、等しいという意味もある。

List<String> actual = Arrays.asList("Hello", "World", "!!");
assertThat(actual, contains("Hello", "World", "!!"));

順序に関係なく各要素がすべて一致(マッチする)するか - containsInAnyOrder()

contains()の順序を無視できる版。

List<String> actual = Arrays.asList("Hello", "World", "!!");
assertThat(actual, containsInAnyOrder("World", "!!", "Hello"));

Collectionの検証

空のCollectionであるか - empty()

Collection#isEmpty()を用いた検証。

Collection<?> actual = Collections.emptyList();
assertThat(actual, is(empty()));

指定した型を要素に持つ空のCollectionであるか - emptyCollectionOf()

empty()に加えて、
バインドされた型も検証する

Collection<String> actual = Collections.emptyList();
assertThat(actual, is(emptyCollectionOf(String.class)));

指定したサイズであるか - hasSize()

Collection#size()を用いた検証。

Collection<String> actual = Arrays.asList("Hello", "World", "!!");
assertThat(actual, hasSize(3));

Mapの検証

指定したキーを持っているか - hasKey()

Map<Integer, String> actual = Collections.singletonMap(1, "Hello");
assertThat(actual, hasKey(1));

指定した値を持っているか - hasValue()

Map<Integer, String> actual = Collections.singletonMap(1, "Hello");
assertThat(actual, hasValue("Hello"));

指定したエントリーを持っているか - hasEntry()

Map<Integer, String> actual = Collections.singletonMap(1, "Hello");
assertThat(actual, hasEntry(1, "Hello"));

このページでは
Matchers(CoreMatchers)の文字列と数値の検証について
注意点と使い方をまとめる。

この他のMatcherの検証については、こちらのページを参照。
(このサイト内の別記事)

文字列の検証
検証内容 メソッド Matchers CoreMatchers
指定した文字列で始まっているか startsWith()
指定した文字列で終わっているか endsWith()
指定した文字列を含んでいるか containsString()
指定した文字列が順番に含まれているかの検証 stringContainsInOrder()
大文字・小文字を無視して一致するか equalToIgnoringCase()
大文字・小文字と空白の長さを無視して一致するか equalToIgnoringWhiteSpace()
""(空文字)であるか isEmptyString()
""(空文字)あるいはnullであるか isEmptyOrNullString()
数値の検証
検証内容 メソッド Matchers CoreMatchers
>(より上)であるか greaterThan()
≧(以上)であるか greaterThanOrEqualTo()
<(より下)であるか lessThan()
≦(以下)であるか lessThanOrEqualTo()
compareTo()がtrueになるか comparesEqualTo()
誤差の範囲内であるか closeTo()

文字列の検証

指定した文字列で始まっているか - startsWith()

String#startsWith()を利用している。

String actual = "Hello World";
assertThat(actual, startsWith("Hello"));

指定した文字列で終わっているか - endsWith()

String#endsWith()を利用している。

String actual = "Hello World";
assertThat(actual, endsWith("World"));

指定した文字列を含んでいるか - containsString()

String#containts()を利用している。

String actual = "Hello World";
assertThat(actual, containsString("or"));

指定した文字列を順番に含んでいるか - stringContainsInOrder()

順番が前後しても検証失敗になる。

String actual = "Hello World";
assertThat(actual, stringContainsInOrder(Arrays.asList("llo", "or")));

大文字・小文字を無視して一致するか - equalToIgnoringCase()

String#equalToIgnoringCase()を利用している。

String actual = "Hello World";
assertThat(actual, equalToIgnoringCase("hello world"));

大文字・小文字と空白の長さを無視して一致するか - equalToIgnoringWhiteSpace()

equalToIgnoringCase()に加えて、
空白の長さも無視する。

ただし、空白自体を完全に無視するわけではない。

String actual = "Hello World";
assertThat(actual, equalToIgnoringWhiteSpace("Hello     World"));

""(空文字)であるか - isEmptyString()

 

String actual = "";
assertThat(actual, isEmptyString());

""(空文字)あるいはnullであるか - isEmptyOrNullString()

""(空文字)に加えて、nullもOK。

String actualEmpty = "";
assertThat(actualEmpty, isEmptyOrNullString());
 
String actualNull = null;
assertThat(actualNull, isEmptyOrNullString());

数値の検証

>(より上)であるか - greaterThan()

 

int actual = 100;
assertThat(actual, greaterThan(99));

≧(以上)であるか - greaterThanOrEqualTo()

 

int actual = 100;
assertThat(actual, greaterThanOrEqualTo(99));
assertThat(actual, greaterThanOrEqualTo(100));

<(より下)であるか - lessThan()

 

int actual = 100;
assertThat(actual, lessThan(101));

≦(以下)であるか - lessThanOrEqualTo()

 

int actual = 100;
assertThat(actual, lessThanOrEqualTo(100));
assertThat(actual, lessThanOrEqualTo(101));

compareTo()がtrueになるか - comparesEqualTo()

例えばBigDecimalなどでは、
equals()は精度まで一致しなければtrueにならなない。

よって、数値として純粋に比較したい場合にis()ではなくcompareTo()の方がいいときは、comparesEqualTo()を使う。

BigDecimal actual = new BigDecimal("20.0");
assertThat(actual, comparesEqualTo(new BigDecimal("20.00")));

誤差の範囲内であるか - closeTo()

特にfloatやdoubleの場合は誤差が生じるので、
equals()を用いるis()を使うと誤差のせいでマッチしないことがある。

そんなときはcloseTo()を利用して、
許容範囲を指定するとうまく検証できる。

double actual = 100.001;
assertThat(actual, is(closeTo(100, 0.01)));

このページでは
Matchers(CoreMatchers)の基本検証と組合せについて
注意点と使い方をまとめる。

基本検証
メソッド 概要 CoreMatchers Matchers
is() eaualsTo()のショートカット or Matcherをそのまま評価
not() 評価の反転(否定)。is()の否定版
nullValue() nullであることの検証
notNullValue() not nullであることの検証
equalsTo() eauals()を用いた検証
sameInstance() 「==」による検証
theInstance() 「==」による検証(sameInstance()と同等)
isA() instanceOf()のショートカット
any() Class.isInstanceOf()による検証(instanceOf()と同等)
instanceOf() Class.isInstanceOf()による検証
anyThing() 常に評価がtrue
組合せ
メソッド 概要 CoreMatchers Matchers
allOf() AND条件
anyOf() OR条件
both() 両方正しいかの検証
either() どちらか正しいかの検証

基本

is() - eaualsTo()のショートカット or Matcherをそのまま評価

オーバーロードにより、
引数がMatcherオブジェクトか否かでどちらかになる。

nullを渡すこともできるが、
nullを検証する場合はnullValue()を利用するのが自然。

assertThat() + Matcherスタイルの中核なので
よく使うことになるが
実はis()の独自処理はほとんど無い。

equals()のショットカット
String expected = "Hello World";
String actual = "Hello" + " " + "World";
assertThat(actual, is(expected));
Matcherをそのまま評価
String actual = "Hello World";
assertThat(actual, is(instanceOf(String.class)));

not() - 評価の反転(否定)

is()の否定版。
is()と組み合わせて使う。

String expected = "Hello World";
String actual = "こんにちは世界";
assertThat(actual, is(not(expected)));

nullValue() - nullであることの検証

オーバーロードの関係上、is(null)とはできない。
よってnull検証専用で存在する。

String actual = null;
assertThat(actual, is(nullValue()));

notNullValue() - not nullであることの検証

not(nullValue())と同等。

String actual = "Hello World";
assertThat(actual, is(notNullValue()));

equalsTo() - equals()による検証

equals()を用いるので
assertEquals()とほぼ同等の検証を行うことができる。

ただ、is()でショートカットされるので、
直接利用することはほとんど無い。

注意点としては、プリミティブ型。
オートボックシングを前提として、
ラッパー型に引き上げた上で検証を行うことになる。

よって、float型やdouble型など誤差が考えられる場合は注意。
(closeTo()などの他の検証方法はある)

String expected = "Hello World";
String actual = "Hello" + " " + "World";
assertThat(actual, is(equalTo(expected)));

sameInstance() - 「==」による検証

同じインスタンスであるかの検証。

Object expected = new Object();
Object actual = expected;
assertThat(actual, is(sameInstance(expected)));

theInstance() - 「==」による検証(sameInstance()と同等)

sameInstance()と同等。
(存在理由がよくわからない。言い回しの違いか…。)

Object expected = new Object();
Object actual = expected;
assertThat(actual, is(theInstance(expected)));

isA() - instanceOf()のショートカット

短いのでこちらを使いたい。

String actual = "Hello World";
assertThat(actual, isA(Object.class));
assertThat(actual, isA(String.class));

any() - Class.isInstanceOf()による検証(instanceOf()と同等)

instanceOf()と同等。

anyThing()やanyOf()とも全く関係ない。
(JMockのスタイルに合わせるものとして存在…?)

String actual = "Hello World";
assertThat(actual, any(Object.class));
assertThat(actual, any(String.class));

instanceOf() - Class.isInstanceOf()による検証

言語仕様のinstanceOfの評価とも同等。

String actual = "Hello World";
assertThat(actual, is(instanceOf(Object.class)));
assertThat(actual, is(instanceOf(String.class)));

anyThing() - 常に評価がtrue

常に評価がtrueになるMatcher。

String actual = "Hello World";
assertThat(actual, is(anything()));

組合せ

allOf() - AND条件

全ての評価がtrueの場合にtrue。

String actual = "Hello World";
assertThat(actual, allOf(startsWith("Hello"), containsString(" "), endsWith("World")));

anyOf() - OR条件

どれか一つの評価がtrueの場合にture。

String actual = "Hello World";
assertThat(actual, anyOf(startsWith("Hello"), startsWith("こんにちは"), startsWith("World")));

both() - 両方正しいかの検証

両方の評価がtrueの場合にtrue。

allOf()でも表現可能。
(2つの評価を強調した表現)

String actual = "Hello World";
assertThat(actual, both(startsWith("Hello")).and(endsWith("World")));

either() - どちらか正しいかの検証

どちらか一方の評価がtrueの場合にture。

anyOf()でも表現可能。
(2つの評価を強調した表現)

String actual = "Hello World";
assertThat(actual, either(startsWith("Hello")).or(startsWith("こんにちは")));


JUnitが出始めのころに使い方を覚えた人は、
assertEquals()やassertTrue()などの方がなじみがある。
しかし、最近はassertThat()を使う方が普通で便利。

assertThat()はJUnit4.4(2007年リリース)から追加された。
JUnit4.4のリリースから5年以上たって、情報も増えてきた。
そろそろassertEquals()は卒業したい。

Maven
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.hamcrest</groupId>
  <artifactId>hamcrest-all</artifactId>
  <version>1.3</version>
  <scope>test</scope>
</dependency>

equals()を検証に用いると不便なことがある

assertEquals()では
「equals()」あるいは「==」を利用した検証になる。

assertEquals()を用いたテスト
String expected = "Hello World";
String actual = "Hello" + " " + "World";
assertEquals(expected, actual);

基本的には問題ない。
これまでこれでやってこられた。

ただ、この「equals()」を利用していることが問題になる。

オブジェクトにequals()は一つしか作れない。
equals()に定義してある内容(状況)と
違う検証をしようとするときに困ってしまう。

これを回避しようと
assertEquals()をフィールドの数分
書いたりする事態になったりするので
やはり困ってしまう。

そもそも「equals()」を必ずしも
定義するとも限らない。

というわけで、なんとなくassertEquals()を使っていたかもしれないが
不便なところ(改善点)も存在する。

検証処理をMatcherとして分離する

先ほどの「equals()」の問題に対処できる
assertThat()とMatcherの組み合わせがJUnit4.4から登場する。

クラス図とコードは次のようになる。

JUnit_クラス図
assertThat()とMatcherを用いたテスト
String expected = "Hello World";
String actual = "Hello" + " " + "World";
assertThat(actual, is(expected));

一見するとあまり変わったように見えない。
actualとexpectedが逆になったぐらい。

ただ、重要なのはassertThat自体は検証処理をしていなくて、
Matcherオブジェクト検証処理を行っていること。
(is()というのはCoreMachersクラスのメソッドでMacherのファクトリ)

このように検証処理をMatcherに分離することで
Matcherを作ってしまえば、オブジェクトのequals()の内容にかかわらず
検証することができるという仕組み。

これによりequals()の問題を回避できる。
(もちろんeauals()を用いた検証も可能)

そして、検証メソッドはassertThatに固定されるので
assertEqualsを何個も何個もという事態も
もちろんなくなる。

この他にも、次のようなメリットがある。

  • 自然な可読
  • 詳細なエラーメッセージ

このようにassertThatとMatcherの方が
assertEquals()より手広くてメリットも多いので
切り替えるべき。

汎用的なMatcherはもちろん提供される

検証処理がMatcherオブジェクトに任されるようになったので
毎回Matcherを作るのかといえば、そんなことにはならない。

汎用的なものはファクトリメソッドで提供されている。

Matcherクラス図

Matcher API はもともとHamcrestというライブラリのものだった。
メジャーなものはバージョン4.4から本家に組み込まれたので
JUnitが利用できるなら使える。

少々マイナーなMatcherもHamcrestのライブラリを
プロジェクトに組み込めば利用できる。

ほかにもサードパーティから提供されるものも存在する。

このように汎用的なものはライブラリに任せて
プロジェクト独自のものは必要なときだけ
Matcherを自作する。

まずはCoreMatchersを使えるようにする

assertThatとMatcherの入りとしては
CoreMatchersに慣れること。

is(),not(),nullValue(),sameInstance()あたりが
使えればよい。

難しいことはないので
スタイルに慣れることが重要。

特にis()がわかればassertEquals()と同等のことは可能なので
スタートはそこから。

これまでassertEquals()で書こうとしたところを
assertThat(actual,is(expected))というように書くことから始めよう。