2014年2月9日日曜日

JUnit用のマッチングするフィールドを指定できるMatcher

JUnit4で使用可能なbeanの一部のフィールドだけでマッチングを行うMatcherです。
JUnit用と言っていますがhamcrestしか使っていません。
ほとんどテストをしていないのですが、作ったことを忘れてしまいそうなので上げておきます。
使用しているライブラリは下記の通りです。
 ・hamcrest-core-1.3.jar
 ・commons-lang3-3.2.1.jar
+各ライブラリで必要なライブラリ

BeanPropertiesMatcher.java
package my.junit.matcher;

import org.hamcrest.Matcher;

/**
 * 一部のプロパティを選択してbeanの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
class BeanPropertiesMatcher<T> extends AbstractBeanPropertiesMatcher<T> {

 /**
  * コンストラクタ
  *
  * @param expected
  *            予想される値
  * @param properties
  *            比較対象のプロパティ
  */
 BeanPropertiesMatcher(T expected, String... properties) {
  super();
  this.expected = expected;
  this.properties = properties;
 }

 /**
  * Matcherを取得します。
  *
  * @param expected
  *            予想される値
  * @param propeties
  *            比較対象のプロパティ
  * @return 一部のプロパティを選択して比較を行えるMatcher
  */
 public static <T> Matcher<T> equalsValueOf(T expected, String... propeties) {
  return new BeanPropertiesMatcher<T>(expected, propeties);
 }

}

BeanIgnorePropertiesMatcher.java
package my.junit.matcher;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.ArrayUtils;
import org.hamcrest.Matcher;

/**
 * 一部のプロパティを無視してbeanの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
class BeanIgnorePropertiesMatcher<T> extends AbstractBeanPropertiesMatcher<T> {

 /**
  * コンストラクタ
  *
  * @param expected
  *            予想される値
  * @param ignoreProperties
  *            比較対象外のプロパティ
  */
 BeanIgnorePropertiesMatcher(T expected, String[] ignoreProperties) {
  super();
  this.expected = expected;
  if(expected == null){
   this.properties = new String[0];
  }else{
   // 全てのFieldから比較対象外のプロパティを除いたプロパティ配列を作る
   List<String> properties = new ArrayList<String>();
   Field[] fields = expected.getClass().getDeclaredFields();
   for (Field field : fields) {
    if (!ArrayUtils.contains(ignoreProperties, field.getName())) {
     properties.add(field.getName());
    }
   }
   this.properties = properties.toArray(new String[properties.size()]);
  }
 }

 /**
  * Matcherを取得します。
  *
  * @param expected
  *            予想される値
  * @param ignoreProperties
  *            比較対象外のプロパティ
  * @return 一部のプロパティを無視して比較を行えるMatcher
  */
 public static <T> Matcher<T> equalsExceptValueOf(T expected, String... ignoreProperties) {
  return new BeanIgnorePropertiesMatcher<T>(expected, ignoreProperties);
 }

}
SelectPropertiesMatcher.java
package my.junit.matcher;

import java.util.List;

import org.hamcrest.Description;
import org.hamcrest.Matcher;

interface SelectPropertiesMatcher<T> extends Matcher<T> {

 /**
  * 予想される値を取得します。
  *
  * @return 予想される値
  */
 T getExpected();

 /**
  * 実際の値を取得します。
  *
  * @return 実際の値
  */
 Object getActual();

 /**
  * 一致しなかったプロパティを取得します。
  *
  * @return 一致しなかったプロパティ
  */
 List<String> getUnmatchProperties();

 /**
  * エラーの場合に表示する、実際の値を示す文字列を追加します。
  *
  * @param description
  *            エラー時の文章
  */
 public void describeMismatch(Description mismatchDescription);

}
AbstractBeanPropertiesMatcher.java
package my.junit.matcher;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.reflect.FieldUtils;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;

/**
 * 一部のプロパティを指定してbeanの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
abstract class AbstractBeanPropertiesMatcher<T> extends TypeSafeMatcher<T>
  implements SelectPropertiesMatcher<T> {

 /**
  * 予想される値
  */
 protected T expected = null;

 /**
  * 実際の値
  */
 protected T actual = null;

 /**
  * 比較対象のプロパティ
  */
 protected String[] properties = null;

 /**
  * 一致しなかったプロパティ
  */
 protected List<String> unmatchProperties = new ArrayList<String>();

 /**
  * コンストラクタ
  */
 protected AbstractBeanPropertiesMatcher() {
 }

 /**
  * 比較を行います。
  *
  * @param actual
  *            実際の値
  * @return 比較結果
  */
 @Override
 public boolean matchesSafely(T actual) {
  this.actual = actual;
  if (expected == actual) {
   // 完全に同じもの
   return true;
  }
  if (expected == null || actual == null) {
   // 片方だけnullだから違う
   return false;
  }
  // 指定のプロパティを取得しながら比較する
  for (String property : properties) {
   Object expectedValue = null;
   Object actualValue = null;
   try {
    // 予測した値
    expectedValue = FieldUtils.readDeclaredField(expected,
      property, true);
    // 実際の値
    actualValue = FieldUtils.readDeclaredField(actual, property,
      true);
   } catch (Exception e) {
    throw new IllegalArgumentException(e);
   }
   if (expectedValue == actualValue) {
    // 完全に同じもの
    continue;
   }
   if (expectedValue == null || actualValue == null) {
    // 片方だけnullだから違う
    unmatchProperties.add(property);
    continue;
   }
   if (!expectedValue.equals(actualValue)) {
    // 違う値
    unmatchProperties.add(property);
    continue;
   }
  }
  // 一致しなかったプロパティがない場合はtrue
  return unmatchProperties.isEmpty();
 }

 /**
  * エラーの場合に表示する、実際の値を示す文字列を追加します。
  *
  * @param description
  *            エラー時の文章
  */
 public void describeMismatch(Description mismatchDescription) {
  describeMismatchSafely(actual, mismatchDescription);
 }

 /**
  * エラーの場合に表示する、実際の値を示す文字列を追加します。
  *
  * @param actual
  *            実際の値
  * @param description
  *            エラー時の文章
  */
 @Override
 public void describeMismatchSafely(T actual, Description mismatchDescription) {
  setUpDescription(mismatchDescription, actual, unmatchProperties);
 }

 /**
  * エラーの場合に表示する、予測の値を示す文字列を追加します。
  *
  * @param description
  *            エラー時の文章
  */
 @Override
 public void describeTo(Description description) {
  setUpDescription(description, expected, unmatchProperties);
 }

 /**
  * エラーの詳細を設定します。
  *
  * @param description
  *            設定先
  * @param bean
  *            対象のbean
  * @param properties
  *            対象のプロパティ名リスト
  */
 private void setUpDescription(Description description, Object bean,
   List<String> properties) {
  if (bean == null) {
   description.appendValue(null);
   return;
  }
  if (properties.isEmpty()) {
   description.appendValue(bean.getClass().getName());
   return;
  }
  int i = -1;
  for (String property : properties) {
   i++;
   if (i != 0) {
    description.appendText(", ");
   }
   description.appendText(property + "=");
   try {
    description.appendValue(FieldUtils.readDeclaredField(bean,
      property, true));
   } catch (IllegalAccessException e) {
    throw new IllegalArgumentException(e);
   }
  }
  return;
 }

 /**
  * 予想される値を取得します。
  *
  * @return 予想される値
  */
 public T getExpected() {
  return expected;
 }

 /**
  * 実際の値を取得します。
  *
  * @return 実際の値
  */
 public Object getActual() {
  return actual;
 }

 /**
  * 一致しなかったプロパティを取得します。
  *
  * @return 一致しなかったプロパティ
  */
 public List<String> getUnmatchProperties() {
  return unmatchProperties;
 }

}


0 件のコメント:

コメントを投稿