Java のアサーションライブラリ AssertJ の時代が来そうな予感
Java のアサーションライブラリに AssertJ というものがある。
http://joel-costigliola.github.io/assertj/
Fluent にアサーションが書けるとのことなので、我がプロジェクトに導入してみました。
基本
基本的な比較。
Junit, Mockito だとこういう風に書くケースが・・・
assertEquals(expect, value); // JUnit assertThat(value, is(expect)); // Mockito
こう書けます。
assertThat(value).isEqualsTo(expect); // AssertJ
「何が嬉しいの?」と思うかもしれませんが、とりあえず先に進みましょう。
文字列比較
よくある比較。コードの意図は説明しなくとも伝わると思います。
assertThat("Hello World").startsWith("Hello"); assertThat("Hello World").contains("llo Wor"); assertThat("Hello World").endsWith("World");
それでも、下記のように Mockito + Hamcrest で書いた場合と比べてそれほど嬉しいようには見えません。若干上記の AssertJ のほうが読みやすいような気はしますが、微々たるものです。
// Mockito + Hamcrest assertThat("Hello World", is(startsWith("Hello"))); assertThat("Hello World", is(containsString("llo Wor"))); assertThat("Hello World", is(endsWith("World")));
真偽値比較
boolean の比較。メソッド名を除けばスタイル的には文字列比較の時とほとんど変わりません。
// AssertJ assertThat(true).isTrue(); assertThat(false).isFalse();
こちらは Mockito 時代のもの。
// Mockito + Hamcrest assertThat(true, is(true)); assertThat(false, is(false));
数値系比較
こちらも今までとほぼ同等。
// AssertJ assertThat(100).isEqualTo(100); assertThat(100).isGreaterThan(10); assertThat(100).isLessThanOrEqualTo(200);
Mockito.
// Mockito + Hamcrest assertThat(100, is(100)); assertThat(100, is(greaterThan(10))); assertThat(100, is(lessThanOrEqualTo(200)));
ファイル系
ファイル系に関しては AssertJ のほうが優れています。
Mockito のデフォルトのマッチャーには File 関連のマッチャーは存在せず、org.hamcrest:hamcrest-library にも存在しないため、デフォルトで用意されている AssertJ のほうが読みやすさで上回っているように見えます。
// AssertJ File file = new File("/tmp"); assertThat(file).exists(); assertThat(file).doesNotExist(); assertThat(file).hasExtension("java");
AssertJ のように hasExtension のようなメソッドが存在しないので、自前でパスの末尾が任意の拡張子で終わっているか確認しています。
// Mockito File file = new File("/tmp"); assertThat(file.exists(), is(true)); assertThat(file.exists(), is(false)); assertThat(file.getAbsolutePath().endsWith(".java"), is(true));
List の比較
そろそろ AssertJ の魅力を知ってもらいましょう。コレクション系の比較がとてもグッドです。
例えば従来のテストでこんなコードがあったとしましょう。
List<String> names = Lists.newArrayList("apple", "orange", "banana"); assertThat(names, contains("apple", "orange", "banana"));
これはほぼ同じようなコードでこう書けます。
// AssertJ List<String> names = Lists.newArrayList("apple", "orange", "banana"); assertThat(names).containsExactly("apple", "orange", "banana");
あまり変わらないですね。ではこっちはどうでしょう。
サイズを比較後、それぞれのユーザーオブジェクトの名前が期待するものかどうか比較してます。
// Mockito + Hamcrest List<User> users = ... ; assertThat(users, hasSize(3)); assertThat(users.get(0).getName(), is("joe")); assertThat(users.get(1).getName(), is("brown")); assertThat(users.get(2).getName(), is("ken"));
これは AssertJ の extracting 機能を使って、各オブジェクトのプロパティを抽出し、リストで比較できるのです。コード量がぐっと減りました。
// AssertJ List<User> users = ... ; assertThat(users).extracting("name").containsExactly("joe", "brown", "ken");
extracting はもっと評価されるべき。
もう一度言います。
extracting はもっと評価されるべき。
Set の比較
単純な文字列や数値の比較ならほぼ同じ。
// Mockito + Hamcrest Set<String> names = Sets.newHashSet("apple", "orange", "banana"); assertThat(names, containsInAnyOrder("apple", "orange", "banana"));
今回の場合は containsOnly というメソッドが使えます。
// AssertJ Set<String> names = Sets.newHashSet("apple", "orange", "banana"); assertThat(names).containsOnly("apple", "orange", "banana");
さて、比較したい要素が String や int ではなく、User クラスのようなオブジェクトだとして、各ユーザーの名前を比較したい場合はどうすれば良いのでしょう?Mockito + Hamcrest ではちょっとキツイです。一旦 Set
// AssertJ Set<User> users = ... ; assertThat(users).extracting("name").containsOnly("joe", "brown", "ken");
さらに extracting は複数のプロパティを抽出でき、タプルのリストとして扱われます。よって、下記のように tuple メソッドを使って比較することもできます。
// AssertJ Set<User> users = ... ; assertThat(users).extracting("name", "age").contains(tuple("ken", 32));
念の為もう一度言っておきましょう。
extracting はもっと評価されるべき。
Map の比較
Map の比較。キーの長さを比較したり、キーの値を比較したり、エントリの比較など。
// Mockito + Hamcrest Map<String, Object> user = new HashMap<>(); user.put("name", "joe"); user.put("age", 27); assertThat(user.keySet(), containsInAnyOrder("name", "age")); assertThat(user.keySet(), hasSize(2)); assertThat(user, hasEntry("name", (Object) "joe")); assertThat(user, hasEntry("age", (Object) 27));
これが AssertJ だとこう書ける。
Map<String, Object> user = new HashMap<>(); user.put("name", "joe"); user.put("age", 27); assertThat(user).containsOnlyKeys("name", "age"); assertThat(user).hasSize(2).containsEntry("name", "joe").containsEntry("age", 27);
そう、AssertJ だとアサーションを連続して書けるのです。
AssertJ の良いところ
コレクション系の比較は AssertJ はよさ気です。でも、上に紹介した部分だけでは、「それだけ?」感は否めません。
でもちゃんと AssertJ にも良いところがあるのです。それは、入力補完が効くところです。
たとえば boolean の比較時、
assertThat(true).isTrue();
というコードを書きましたが、下記のようには書けません。
assertThat("String").isTrue(); // compile error!
つまり assertThat(...) の次に続くメソッドは assertThat(...) の ... の型に応じて分岐しているので、IDE と組み合わせればアサーションを書く時が以前よりスムーズになるのです。書いた後のコードは mockito を用いた時のコードとあまり差が無くとも、書く時の快適さが AssertJ は優れているのです。
例えば下記のように、IDE が補完してくれるので、具体的なメソッド名を知らなかったりしててもだいたい何とかなるのです。
contains 系を調べたい時も、こんな感じで。
AssertJ の良いところ2
ここまで Mockito vs AssertJ のように書いてきましたが、この2つは共存できます。
よって、コレクション系の比較が多い箇所だけ AssertJ を導入する、というような使い方でも大丈夫。理想を言えば依存するライブラリは少ないほうが良いのですが、Mockito にも mock や ArgumentCaptor 等素晴らしい機能があるので、ここはひとつ両者のいいとこ取り、というセンでどうでしょう。弊社のプロジェクトでもそうしており、現状特に問題は起きてません。
まとめ
のでさくっと導入して良いのではないでしょうか。
システムテスト自動化 標準ガイド CodeZine BOOKS
- 作者: Mark Fewster,Dorothy Graham
- 出版社/メーカー: 翔泳社
- 発売日: 2014/12/17
- メディア: Kindle版
- この商品を含むブログ (3件) を見る