とりあえず、車輪の再発明な気がします。
どっかにきっとあると思うんだ。
以下、本題です。
hamcrestパッケージの叙述的?記法は結構気に入ってるのですが、
Date系のMatcherがあんまりなくて、今のテストだと、
年だけとか、月だけとか、日だけの検証がしたくなってきました。
hamcrestのMatcherクラスは結構簡単に拡張出来るので書いてみました。
以下のメソッドを利用して比較出来ます
// 年・月・日が正しいことを比較
assertThat(Date, isDate(Date));
// 年が正しいことを比較
assertThat(Date, isYear(int));
// 月が正しいことを比較
assertThat(Date, isMonth(int));
// 日が正しいことを比較
assertThat(Date, isDay(int));
以下ソースです。
import java.util.Calendar;
import java.util.Date;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
/**
* Date比較用のMatcherクラス.
* 年、月、日、あるいは年月日を対象に値の検証を行う
* @author morikawa
*/
public class DateMatcher extends BaseMatcher<Date> {
/** Matcher対象のオブジェクト */
private Date entity;
/** 出力メッセージ */
private StringBuilder message;
/** 検証対象フィールド */
private DateField field;
/** 比較フィールドの列挙型 **/
public static enum DateField {
/** インスタンス自体の比較 */
ALL {
@Override
public boolean is(Date base, Date target) {
return YEAR.is(base, target) && MONTH.is(base, target) && DAY.is(base, target);
}
@Override
public void addDescription(Description description, Date target) {
description.appendValue(target);
}
},
/** 年比較 */
YEAR {
@Override
public boolean is(Date base, Date target) {
Calendar baseCal = Calendar.getInstance();
Calendar targetCal = Calendar.getInstance();
baseCal.setTime(base);
targetCal.setTime(target);
return baseCal.get(Calendar.YEAR) == targetCal.get(Calendar.YEAR);
}
@Override
public void addDescription(Description description, Date target) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(target);
description.appendValue("YEAR::" + calendar.get(Calendar.YEAR));
}
},
/** 月比較 */
MONTH {
@Override
public boolean is(Date base, Date target) {
Calendar baseCal = Calendar.getInstance();
Calendar targetCal = Calendar.getInstance();
baseCal.setTime(base);
targetCal.setTime(target);
return baseCal.get(Calendar.MONTH) == targetCal.get(Calendar.MONTH);
}
@Override
public void addDescription(Description description, Date target) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(target);
description.appendValue("MONTH::" + calendar.get(Calendar.MONTH));
}
},
/** 日比較 */
DAY {
@Override
public boolean is(Date base, Date target) {
Calendar baseCal = Calendar.getInstance();
Calendar targetCal = Calendar.getInstance();
baseCal.setTime(base);
targetCal.setTime(target);
return baseCal.get(Calendar.DAY_OF_MONTH) == targetCal.get(Calendar.DAY_OF_MONTH);
}
@Override
public void addDescription(Description description, Date target) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(target);
description.appendValue("DAY_OF_MONTH::" + calendar.get(Calendar.DAY_OF_MONTH));
}
};
/**
* 値が同じであることの比較を行う。
* @param base 比較元のDate
* @param target 比較対象のDate
* @return 値が同一の場合にtrue
*/
abstract boolean is(Date base, Date target);
/**
* Matcherに検証対象オブジェクトの情報を設定する
* @param description 概要
* @param date 対象のDate
*/
abstract void addDescription(Description description, Date target);
}
public DateMatcher(Date date, DateField field) {
this.entity = date;
if (field == null) {
this.field = DateField.ALL;
} else {
this.field = field;
}
this.message = new StringBuilder();
}
/**
* 年月日レベルで比較を行う
* @param date 比較対象のDate
* @return 年月日比較で用いられる{@link DateMatcher}
*/
public static DateMatcher isDate(Date date) {
return new DateMatcher(date, DateField.ALL);
}
/**
* 年比較を行う
* @param year YYYY形式の日付
* @return 年比較で用いられる{@link DateMatcher}
*/
public static DateMatcher isYear(int year) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, year);
return new DateMatcher(calendar.getTime(), DateField.YEAR);
}
/**
* 月比較を行う
* @param month 1~12までの月
* @return 月比較を行う{@link DateMatcher}
*/
public static DateMatcher isMonth(int month) {
if (month > 12 || month < 1) {
throw new IllegalArgumentException("対象外の月が設定されています");
}
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.MONDAY, month -1);
return new DateMatcher(calendar.getTime(), DateField.MONTH);
}
/**
* 日比較を行う
* @param day 1~31までの日
* @return 日比較を行う{@link DateMatcher}
*/
public static DateMatcher isDay(int day) {
if (day > 31 || day < 1) {
throw new IllegalArgumentException("対象外の日が設定されています");
}
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DAY_OF_MONTH, day);
return new DateMatcher(calendar.getTime(), DateField.DAY);
}
/**
* {@inheritDoc}
* @see org.hamcrest.Matcher#matches(java.lang.Object)
*/
public boolean matches(Object item) {
Date date = (Date) item;
if (this.entity == null ^ date == null) {
if (this.entity == null) {
this.message.append(" -> entity is null.");
} else {
this.message.append(" -> date is null.");
}
return false;
}
if (this.entity == null && date == null) {
//TODO とりあえずどちらもnullの場合はfalseを返してメッセージを設定する
this.message.append(" -> entity date both is null.");
return false;
}
return this.field.is(this.entity, date);
}
/**
* {@inheritDoc}
* @see org.hamcrest.SelfDescribing#describeTo(org.hamcrest.Description)
*/
public void describeTo(Description description) {
// description.appendValue(this.entity);
this.field.addDescription(description, this.entity);
description.appendText(this.message.toString());
}
}
ちょっと補足すると、DateMatcher#describeToでは、標準出力に出力する文字列を定義出来ます。
たとえばEclipseのJUnitでAssertionErrorが発生すると、以下のような出力が行われます。
java.lang.AssertionError:
Expected:
got:
description.appendValue(Object)でExpected:の値の部分に文字列が、
description.appendText(String)で上記に続く文字列が設定出来ます。
で、DateMatcherを作ったらテストクラスもあるわけで、以下の通りです。
import java.lang.reflect.Field;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
@RunWith(Parameterized.class)
public class DateMatcherTest {
@Parameters
public static Collection methods() {
return Arrays.asList(new Object[][] {
{null},
{DateField.ALL},
{DateField.YEAR},
{DateField.MONTH},
{DateField.DAY}
});
}
private DateField field;
public DateMatcherTest(DateField field) {
this.field = field;
}
// 初期化の検証
@Test
public void コンストラクタの検証() throws Exception {
DateMatcher matcher = new DateMatcher(new Date(), field);
DateField field = this.<DateField>getPrivateProperty(matcher, "field");
assertThat(field, is(field));
}
@Test
public void コンストラクタの検証_null() throws Exception {
DateMatcher matcher = new DateMatcher(new Date(), null);
DateField field = this.<DateField>getPrivateProperty(matcher, "field");
assertThat(field, is(DateField.ALL));
}
@Test
public void isDateによるインスタンス生成() throws Exception {
DateMatcher matcher = DateMatcher.isDate(new Date());
DateField field = this.<DateField>getPrivateProperty(matcher, "field");
assertThat(field, is(DateField.ALL));
}
@Test
public void isYearによるインスタンス生成() throws Exception {
DateMatcher matcher = DateMatcher.isYear(2010);
DateField field = this.<DateField>getPrivateProperty(matcher, "field");
assertThat(field, is(DateField.YEAR));
}
@Test
public void isMonthによるインスタンス生成() throws Exception {
DateMatcher matcher = DateMatcher.isMonth(11);
DateField field = this.<DateField>getPrivateProperty(matcher, "field");
assertThat(field, is(DateField.MONTH));
}
@Test
public void isDayによるインスタンス生成() throws Exception {
DateMatcher matcher = DateMatcher.isDay(10);
DateField field = this.<DateField>getPrivateProperty(matcher, "field");
assertThat(field, is(DateField.DAY));
}
@Test(expected=IllegalArgumentException.class)
public void isMonth利用時の境界値の検証_MIN() {
DateMatcher matcher = DateMatcher.isMonth(0);
}
@Test(expected=IllegalArgumentException.class)
public void isMonth利用時の境界値の検証_OVER() {
DateMatcher matcher = DateMatcher.isMonth(13);
}
@Test(expected=IllegalArgumentException.class)
public void isDay利用時の境界値の検証_MIN() {
DateMatcher matcher = DateMatcher.isMonth(0);
}
@Test(expected=IllegalArgumentException.class)
public void isDay利用時の境界値の検証_OVER() {
DateMatcher matcher = DateMatcher.isMonth(32);
}
// Matcherの検証
@Test
public void isDateの検証() {
Date d = new Date();
Date d1 = DateUtil.getDay(d);
assertThat(d1, DateMatcher.isDate(d));
}
@Test
public void isDateの検証_false() {
Date d = new Date();
Date d1 = DateUtil.getDay(DateUtil.moveDay(d, 1));
assertThat(d1, not(DateMatcher.isDate(d)));
}
@Test
public void isYearの検証_true() throws ParseException {
Date d = DateFormater.toDate("2010/11/12");
assertThat(d, DateMatcher.isYear(2010));
}
@Test
public void isYearの検証_false() throws ParseException {
Date d = DateFormater.toDate("2010/11/12");
assertThat(d, not(DateMatcher.isYear(2011)));
}
@Test
public void isMonthの検証_true() throws ParseException {
Date d = DateFormater.toDate("2010/11/12");
assertThat(d, DateMatcher.isMonth(11));
}
@Test
public void isMonthの検証_false() throws ParseException {
Date d = DateFormater.toDate("2010/11/12");
assertThat(d, not(DateMatcher.isMonth(10)));
}
@Test
public void isDayの検証_true() throws ParseException {
Date d = DateFormater.toDate("2010/11/12");
assertThat(d, DateMatcher.isDay(12));
}
@Test
public void isDayの検証_false() throws ParseException {
Date d = DateFormater.toDate("2010/11/12");
assertThat(d, not(DateMatcher.isDay(11)));
}
@Test
public void null値を含む検証0() {
DateMatcher matcher = new DateMatcher(new Date(), null);
assertThat(matcher.matches(null), is(false));
}
@Test
public void null値を含む検証1() {
DateMatcher matcher = new DateMatcher(null, null);
assertThat(matcher.matches(new Date()), is(false));
}
@Test
public void null値を含む検証2() {
DateMatcher matcher = new DateMatcher(null, null);
assertThat(matcher.matches(null), is(false));
}
// ユーティリティ
// privateフィールドをリフレクションで取得する。
public static <T> T getPrivateProperty(DateMatcher matcher, String propertyName) throws Exception {
Field field = matcher.getClass().getDeclaredField(propertyName);
field.setAccessible(true);
return (T) field.get(matcher);
}
}
良ければ使ってみてください。