最初は
enum
に関してエントリーを書こうとしていたのですが、そのネタについてコードを書いている途中で、どうしても気持ち悪いテストコードを書く羽目になったので、なんかいい表現ないかなと考えているうちに、hamcrest
の拡張を書いてしまいました。というわけで、タイトル
hamcrestを拡張してmoreThanとか作ってみた
です。
そもそものそもそも
もともと書いていた気持ち悪いテストコード…
@Test
public void testCompare() {
Trump queen = Trump.valueOf("Queen");
Trump king = Trump.valueOf("King");
assertThat(queen.compareTo(king) < 0, is(true));
}
private enum Trump implements Comparable<Trump> {
King {
int value = 13;
@Override
public int value() {
return value;
}
@Override
public int compareTo(Trump trump) {
return this.value() - trump.value();
}
}, Queen {
int value = 12;
@Override
public int value() {
return value;
}
@Override
public int compareTo(Trump trump) {
return this.value() - trump.value();
}
}
abstract public int value();
abstract public int compareTo(Trump trump);
}
assertThat
の中が気持ち悪い…まあ、別に
boolean result = queen.compareTo(king) < 0
を変数として取り出せばよいだけの話といえば、それまでなのですが、やっぱりなんか気持ち悪い。で、
junit more than
でググってみたけど、こんな感じでした(´・ω・`)。というわけで、
moreThan
みたいなことをやりたかったので、自前で作って見ることにしました。拡張してみよう!
で、コードはこんな感じ。
org.hamcrest.core.MoreThan.java
package org.hamcrest.core;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
public class MoreThan<T extends Comparable<T>> extends BaseMatcher<T> {
private final T matcher;
private final Class<T> klass;
public MoreThan(T matcher) {
this.matcher = matcher;
this.klass = (Class<T>) matcher.getClass();
}
@Override
public boolean matches(Object o) {
if(o.getClass() == klass) {
T object = klass.cast(o);
int result = matcher.compareTo(object);
return result < 0;
} else {
return false;
}
}
@Override
public void describeTo(Description description) {
description.appendText("more than ").appendValue(matcher);
}
@Factory
public static <T extends Comparable<T>> Matcher<T> moreThan(T value) {
return new MoreThan<T>(value);
}
}
拡張して自分好みの
Matcher
を作るにはorg.hamcrest.BaseMatcher<T>
を継承するようです。メソッド
matches
は実際の値の比較を記述します。メソッド
describeTo
はテストが失敗したときに表示される文章を記述します。使い方
使い方は至って簡単です。
MoreThanTest.java
@Test
public void testCalendarCase() {
Calendar cal1 = Calendar.getInstance(
TimeZone.getTimeZone("Asia/Tokyo"));
cal1.set(2011, 12, 24, 15, 54, 55);
Calendar cal2 = Calendar.getInstance(
TimeZone.getTimeZone("Asia/Tokyo"));
cal2.set(2011, 12, 24, 15, 54, 56);
assertThat(cal2, moreThan(cal1));
}
ちなみに、残念な所があります。
int
とlong
など、異なる型の値を比較できない。
まあ、そのあたりは型安全様にあわせてあげて下さい。
その他、
LessThan
、LessThanEqual
、MoreThanEqual
なども作ってみましたので、まあ、興味があったら見てツッコミを下さい。明日のJava Adventカレンダーは、daisuke-mさんです。
余談
このエントリーを掲載した後、こんなツイートが寄せられました。
うぉ、本当だ…!
さらには、
なんと!。
JUnit4
にバンドルされているhamcrestは古いバージョンだと!そして、極めつけは、
というわけで、明日のdaisuke_mさんのページを見ると…
都元ダイスケ IT-PRESS : [Java][test]hamcrestのMatcherメモ
バッチリまとめられておる。
というわけで、車輪の再発明をしてしまったようだ…。
そこで、ソースを読んでみる
まあ、情弱なのは仕方ないので、もうちょい突っ込んでみてみる。
hamcrest-library
のソースはcode.google.com
にあるようです。org.hamcrest.Matchers.java
というソースはなかったのですが、その本体となるorg.hamcrest.number.OrderingComparison
というクラスがあったので、それを読んでみました。
/* Copyright (c) 2000-2009 hamcrest.org
*/
package org.hamcrest.number;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
public class OrderingComparison> extends TypeSafeMatcher {
private static final int LESS_THAN = -1;
private static final int GREATER_THAN = 1;
private static final int EQUAL = 0;
private final T expected;
private final int minCompare, maxCompare;
private static final String[] comparisonDescriptions = {
"less than",
"equal to",
"greater than"
};
private OrderingComparison(T expected, int minCompare, int maxCompare) {
this.expected = expected;
this.minCompare = minCompare;
this.maxCompare = maxCompare;
}
@Override
public boolean matchesSafely(T actual) {
int compare = Integer.signum(actual.compareTo(expected));
return minCompare <= compare && compare <= maxCompare;
}
@Override
public void describeMismatchSafely(T actual, Description mismatchDescription) {
mismatchDescription.appendValue(actual).appendText(" was ")
.appendText(asText(actual.compareTo(expected)))
.appendText(" ").appendValue(expected);
}
public void describeTo(Description description) {
description.appendText("a value ").appendText(asText(minCompare));
if (minCompare != maxCompare) {
description.appendText(" or ").appendText(asText(maxCompare));
}
description.appendText(" ").appendValue(expected);
}
private String asText(int comparison) {
return comparisonDescriptions[comparison + 1];
}
/**
* @return Is value = expected?
*/
@Factory
public static > Matcher comparesEqualTo(T value) {
return new OrderingComparison(value, EQUAL, EQUAL);
}
/**
* Is value > expected?
*/
@Factory
public static > Matcher greaterThan(T value) {
return new OrderingComparison(value, GREATER_THAN, GREATER_THAN);
}
/**
* Is value >= expected?
*/
@Factory
public static > Matcher greaterThanOrEqualTo(T value) {
return new OrderingComparison(value, EQUAL, GREATER_THAN);
}
/**
* Is value < expected?
*/
@Factory
public static > Matcher lessThan(T value) {
return new OrderingComparison(value, LESS_THAN, LESS_THAN);
}
/**
* Is value <= expected?
*/
@Factory
public static > Matcher lessThanOrEqualTo(T value) {
return new OrderingComparison(value, LESS_THAN, EQUAL);
}
}
@Factory
のあたりを眺めてから、describeTo
などを眺めると、思わずニヤリとしてしまいますね。やっぱり本家本元のソースには勝てんかったか…。
で、なんで、
Matchers
がないのか気になったのですが、ひょっとしてhamcrest-generator
あたりで自動で処理しているのかな~などと思ってみたりしましたが、まだそこまでちゃんとソースを読んでおりません…ん~、まあ、でも少し勉強になりました。
ご指摘をくださった皆様、大変有難う御座います。
0 件のコメント:
コメントを投稿