前回の記事「JUnit4 Matcherの使い方(前編)」では、JUnitの基本機能であるMatcherの使い方について解説しました。
汎用的なチェックならば、その記事で紹介した既存Matcherの機能で対応出来るでしょう。
しかし、「汎用的チェックではなく、プロジェクト特有なチェックはどうすればいいの?」という疑問が残ったかと思います。
今回の記事「JUnit4 Matcherの使い方(後編)」では、そんなプロジェクト個別の要望にお応えする機能「カスタムMatcher」を紹介します。
さて、「カスタムMatcher」というのは、要するに自分でMatcherを作るという意味です。
「自分で作らなきゃならないのであれば意味無いじゃないか」と思われる方もいらっしゃるかもしれませんが、そんな事はありません。
確かに自分で作る手間こそ存在しますが、JUnit標準に従うことでソースの可読性が上がったり、ソースを再利用出来たりと、基本ですが非常に大事なメリットが得られるのです。
実例:シンプル編
では、例を挙げてご説明しましょう。簡単な例として、「数字が偶数かチェック」という機能を実現してみます。
まず、この「偶数チェック」を既存Macherでチェックすると以下のようになります。
/** * 数字が偶数かチェック */ @Test public void test偶数チェック() { int num = 10; assertThat(num % 2, comparesEqualTo(0)); }
この程度なら、別に何も難しいことはありません。
数字を2で割って、余りが0であれば偶数というロジックです。
このテストがこの1回限りであれば、これで済ませてもOKです。
しかし、プロジェクトの性質により「複数のテストケースで何度も偶数チェックを行わなければならない」という事情があるとすればどうでしょう?
あちこちに同じ「num % 2」のロジックをコピペして開発していくのでしょうか?
それはちょっとソースとして美しくありません。
そこで登場するのがカスタムMatcherです。
カスタムMatcherは「org.hamcrest.BaseMatcher」か「org.hamcrest.TypeSafeMatcher」を継承して拡張することで作成出来ます。
どちらを継承すれば良いのか、という点で悩みますが、BaseMatcherにタイプセーフ機能を追加して便利になっているのがTypeSafeMatcherですので、普通はこちらを使えば良いでしょう。
こうしてTypeSafeMatcherを継承して作ったカスタムMatcherがこちらです。
import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; /** * 数字が偶数であることをチェック * * @author 技術開発事業部 遠藤 太志郎 * */ public class EvenNum extends TypeSafeMatcher<Integer> { @Override protected boolean matchesSafely(Integer item) { return item % 2 == 0; } @Override public void describeTo(Description description) { description.appendText("<偶数>"); } }
「matchesSafelyメソッド」の引数「item」にチェック対象値が入ってきますので、これに対しチェック条件を記述します。
この結果が「false」の時、describeToに進みます。
「descriveToメソッド」はエラー発生時の文言を設定する部分です。
これでカスタムMatcherが作成出来ました。
これを使ってテストメソッドを書くと以下になります。
/** * 数字が偶数かチェック */ @Test public void test偶数チェック() { int num = 10; assertThat(num, is(new EvenNum())); }
「数字を2で割って余りをチェック」というロジックをカスタムMatcher側に持たせることが出来たので、テストケースを作る人はただEvenNumを呼び出すだけで良くなりました。
このEvenNumがエラーになると以下のように文言が表示されます。
表示文言も自分でカスタマイズ出来るので分かりやすいですね。
実例:応用編
次に、これを使った応用編に行ってみましょう。「エンティティやDTOのチェック」です。
例えばデータベースから何かを検索した時の検索結果は、大抵の場合は「String型のパラメータ単体」ではなくて、複数のパラメータをフィールドに持つエンティティやDTOの形で取得されるものです。
例として、以下に商品エンティティを定義します。
特に何の特徴も無い、タダのデータのパッケージというだけのクラスです。
package jp.co.net.genesis.junit.sample.custommatcherテスト; /** * 商品エンティティ * * @author 技術開発事業部 遠藤 太志郎 * */ public class Shohin { /** 商品コード */ private int shohinCode; /** 商品名 */ private String shohinName; /** 価格 */ private int shohinPrice; /** * 商品コードを取得します。 * @return 商品コード */ public int getShohinCode() { return shohinCode; } /** * 商品コードを設定します。 * @param shohinCode 商品コード */ public void setShohinCode(int shohinCode) { this.shohinCode = shohinCode; } /** * 商品名を取得します。 * @return 商品名 */ public String getShohinName() { return shohinName; } /** * 商品名を設定します。 * @param shohinName 商品名 */ public void setShohinName(String shohinName) { this.shohinName = shohinName; } /** * 価格を取得します。 * @return 価格 */ public int getShohinPrice() { return shohinPrice; } /** * 価格を設定します。 * @param shohinPrice 価格 */ public void setShohinPrice(int shohinPrice) { this.shohinPrice = shohinPrice; } /* (非 Javadoc) * @see java.lang.Object#toString() */ public String toString() { StringBuilder bul = new StringBuilder(); bul.append("商品コード=").append(shohinCode).append(","); bul.append("商品名=").append(shohinName).append(","); bul.append("価格=").append(shohinPrice); return bul.toString(); } }
商品テーブルを検索して、このエンティティで結果が取れてくると定義しまして、商品テーブルを主キーで検索した結果が正しいことをチェックするテストケースは、既存Matcherで実現するなら以下になります。
/** * 商品テーブルを主キーで検索する */ @Test public void test_商品テーブルを主キーで検索する() { ShohinService service = new ShohinService(); Shohin actual = service.findById(123); Shohin expected = new Shohin(); expected.setShohinCode(123); expected.setShohinName("鉛筆"); expected.setShohinPrice(105); assertThat(actual.getShohinCode(), is(expected.getShohinCode())); assertThat(actual.getShohinName(), is(expected.getShohinName())); assertThat(actual.getShohinPrice(), is(expected.getShohinPrice())); }
このソースの問題は以下の部分です。
- assertThat(actual.getShohinCode(), is(expected.getShohinCode()));
- assertThat(actual.getShohinName(), is(expected.getShohinName()));
- assertThat(actual.getShohinPrice(), is(expected.getShohinPrice()));
このテストケースは例として「主キー検索」を挙げています。
しかし、実際の開発では「商品名で検索」「商品名を前方一致で検索」「価格が一定数値以下で検索」など、その他色々な機能に対してテストする必要が出てきます。
それら全てに対して全部同じコピペを繰り返しては、実に泥臭いソースになってしまいます。
もちろん、今回で例にしている商品エンティティのフィールド数はたったの3つですけれども、実際の開発では10や20もフィールドがあるなんて普通のことです。
それをこんな風にコピペ連発で作っていたら、それだけでソースが埋まってしまいます。その中にコピペミスが紛れ込んでいる可能性も少なくありません。
汚いソースコードはバグの温床です。
ここはカスタムMatcherで綺麗にしましょう!!
/** * Shohinエンティティが一致していることをチェック * * @author 技術開発事業部 遠藤 太志郎 * */ public class EqualToShohin extends TypeSafeMatcher{ /** 期待値 */ private Shohin expected; /** 異なる値 */ private String difference; public EqualToShohin(Shohin expected){ this.expected = expected; } @Override protected boolean matchesSafely(Shohin actual) { //商品コード一致チェック if(actual.getShohinCode() != expected.getShohinCode()){ difference = "商品コード"; return false; } //商品名一致チェック if(!actual.getShohinName().equals(expected.getShohinName())){ difference = "商品名"; return false; } //商品価格一致チェック if(actual.getShohinPrice() != expected.getShohinPrice()){ difference = "商品価格"; return false; } return true; } @Override public void describeTo(Description description) { description.appendValue(expected); description.appendText(difference).appendText("が異なっています。"); } }
処理の要点は以下です。
- コンストラクタで期待値となる商品エンティティを渡す。
- 「matchesSafely」で各カラムをチェックする。異なっている箇所がある場合はfalseを返し、全部正常であればtrueを返す。
- 「describeTo」でエラー時に表示するメッセージを形成する。オブジェクトのメッセージは、そのオブジェクトの「toString()」メソッドで表示されるため、予め商品エンティティでは「toString()」をオーバーライドして分かりやすい表示を作っておく。
これだけです。
特に難しいことはありません。
このカスタムMatcherでfalseになった場合、以下のようなメッセージが表示されます。
/** * 商品テーブルを主キーで検索する */ @Test public void test_商品テーブルを主キーで検索する_EqualToShohin() { ShohinService service = new ShohinService(); Shohin actual = service.findById(123); Shohin expected = new Shohin(); expected.setShohinCode(123); expected.setShohinName("鉛筆"); expected.setShohinPrice(104); assertThat(actual, is(new EqualToShohin(expected))); }
無事に「assertThat」一行でチェック出来るようになりました。
やはりJavaのソースは、このようにオブジェクト単位で操作出来るのが美しいですね。
最初にカスタムMatcherを作るのが少々面倒だと感じられたかもしれませんが、この最初の一手間を惜しまずにキッチリやっておけば、以降のテストケース作成が非常に楽になります。
上流工程で頑張っておけば下流工程が楽になるのは、メインソースもテストソースも同じです。
JUnit開発は「所詮はテストソースだし」ということでメインソースよりも品質が劣悪になることが多いですが、それは自縄自縛というもの。
テストソースもメインソースと同じ。最初にクラス設計を頑張っておくことでソースが綺麗になり、品質が上がり、全体的に効率化されて、後々楽になってくるのです。
このカスタムMacherを駆使して、ぜひ綺麗で強固なシステムを作り上げて下さい。
0 件のコメント:
コメントを投稿