2014年2月11日火曜日

JUnit用のテキストファイルを比較するMatcher

JUnit4で使用可能なテキストファイル同士の内容をマッチングするMatcherです。
JUnit用と言っていますがhamcrestしか使っていません。
取りあえず作ってみた状態なのでテストはせず、ちょっと動かしてみた程度です。直さなければいけない部分もあるかと思いますが、作ったことを忘れてしまいそうなので上げておきます。
使用しているライブラリは下記の通りです。
 ・hamcrest-core-1.3.jar
+各ライブラリで必要なライブラリ

TextInputStreamMatcher.java
package my.junit.matcher;

import java.io.InputStream;

import org.hamcrest.Matcher;

/**
 * テキスト形式でInputStreamの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
class TextInputStreamMatcher extends AbstractTextFileMatcher<InputStream> {

 /**
  * コンストラクタ
  */
 protected TextInputStreamMatcher(InputStream expected) {
  this(expected, CHAR_SET);
 }

 /**
  * コンストラクタ
  */
 protected TextInputStreamMatcher(InputStream expected, String charSet) {
  super();
  this.charSet = charSet;
  expectedName = "expected file";
  expectedIs = expected;
 }

 /**
  * <pre>
  * テキストファイル比較用のMatcherを取得します。
  * キャラクターセットはUTF-8です。
  * </pre>
  *
  * @param expected
  *            予想されるファイル
  * @return テキストファイル比較用のMatcher
  */
 public static Matcher<InputStream> equalsContentsOf(InputStream expected) {
  return new TextInputStreamMatcher(expected);
 }

 /**
  * テキストファイル比較用のMatcherを取得します。
  *
  * @param expected
  *            予想されるファイル
  * @param charSet
  *            キャラクターセット
  * @return テキストファイル比較用のMatcher
  */
 public static Matcher<InputStream> equalsContentsOf(InputStream expected,
   String charSet) {
  return new TextInputStreamMatcher(expected, charSet);
 }

 /**
  * {@inheritDoc}
  */
 @Override
 protected InputStream getActualIs(InputStream actual) {
  actualIs = actual;
  return actualIs;
 }

}


TextFileMatcher.java
package my.junit.matcher;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

import org.hamcrest.Matcher;

/**
 * テキストファイルの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
class TextFileMatcher extends AbstractTextFileMatcher<File> {

 /**
  * コンストラクタ
  */
 protected TextFileMatcher(File expected) {
  this(expected, CHAR_SET);
 }

 /**
  * コンストラクタ
  */
 protected TextFileMatcher(File expected, String charSet) {
  super();
  this.charSet = charSet;
  File expectedFile = expected;
  expectedName = expectedFile.getPath();
  try {
   expectedIs = new FileInputStream(expectedFile);
  } catch (FileNotFoundException e) {
   throw new IllegalArgumentException(e);
  }
 }

 /**
  * <pre>
  * テキストファイル比較用のMatcherを取得します。
  * キャラクターセットはUTF-8です。
  * </pre>
  *
  * @param expected
  *            予想されるファイル
  * @return テキストファイル比較用のMatcher
  */
 public static Matcher<File> equalsContentsOf(File expected) {
  return new TextFileMatcher(expected);
 }

 /**
  * テキストファイル比較用のMatcherを取得します。
  *
  * @param expected
  *            予想されるファイル
  * @param charSet
  *            キャラクターセット
  * @return テキストファイル比較用のMatcher
  */
 public static Matcher<File> equalsContentsOf(File expected, String charSet) {
  return new TextFileMatcher(expected, charSet);
 }

 /**
  * {@inheritDoc}
  */
 @Override
 protected InputStream getActualIs(File actual) {
  actualName = actual.getPath();
  try {
   actualIs = new FileInputStream(actual);
  } catch (FileNotFoundException e) {
   throw new IllegalArgumentException(e);
  }
  return actualIs;
 }

}


TextFilePathMatcher.java
package my.junit.matcher;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

import org.hamcrest.Matcher;

/**
 * テキストファイルの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
class TextFilePathMatcher extends AbstractTextFileMatcher<String> {

 /**
  * コンストラクタ
  */
 protected TextFilePathMatcher(String expected) {
  this(expected, CHAR_SET);
 }

 /**
  * コンストラクタ
  */
 protected TextFilePathMatcher(String expected, String charSet) {
  super();
  this.charSet = charSet;
  String expectedPath = (String) expected;
  File expectedFile = new File(expectedPath);
  expectedName = expectedFile.getPath();
  try {
   expectedIs = new FileInputStream(expectedFile);
  } catch (FileNotFoundException e) {
   throw new IllegalArgumentException(e);
  }
 }

 /**
  * <pre>
  * テキストファイル比較用のMatcherを取得します。
  * キャラクターセットはUTF-8です。
  * </pre>
  *
  * @param expected
  *            予想されるファイルのパス
  * @return テキストファイル比較用のMatcher
  */
 public static Matcher<String> equalsContentsOf(String expected) {
  return new TextFilePathMatcher(expected);
 }

 /**
  * テキストファイル比較用のMatcherを取得します。
  *
  * @param expected
  *            予想されるファイルのパス
  * @param charSet
  *            キャラクターセット
  * @return テキストファイル比較用のMatcher
  */
 public static Matcher<String> equalsContentsOf(String expected,
   String charSet) {
  return new TextFilePathMatcher(expected, charSet);
 }

 /**
  * {@inheritDoc}
  */
 @Override
 protected InputStream getActualIs(String actual) {
  File actualFile = new File(actual);
  actualName = actualFile.getPath();
  try {
   actualIs = new FileInputStream(actualFile);
  } catch (FileNotFoundException e) {
   throw new IllegalArgumentException(e);
  }
  return actualIs;
 }

}


AbstractTextFileMatcher.java
package my.junit.matcher;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;

/**
 * テキストファイルの比較を行えるMatcherの抽象クラス
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
abstract class AbstractTextFileMatcher<T> extends TypeSafeMatcher<T> {

 /**
  * デフォルトキャラクターセット
  */
 protected static final String CHAR_SET = "UTF-8";

 /**
  * キャラクターセット
  */
 protected String charSet = null;

 /**
  * 予想されるファイルの表示名
  */
 protected String expectedName = null;

 /**
  * 予想されるファイルのインプットストリーム
  */
 protected InputStream expectedIs = null;

 /**
  * 実際のファイルの表示名
  */
 protected String actualName = null;

 /**
  * 実際のファイルのインプットストリーム
  */
 protected InputStream actualIs = null;

 /**
  * 不一致だった予想されるファイルの行
  */
 protected List<String> unmatchedExpectedLines = new ArrayList<String>();

 /**
  * 不一致だった実際のファイルの行
  */
 protected List<String> unmatchedActualLines = new ArrayList<String>();

 /**
  * 不一致だった行番号
  */
 protected List<Integer> unmatchedLineNums = new ArrayList<Integer>();

 /**
  * デフォルトコンストラクタ
  */
 protected AbstractTextFileMatcher() {
  super();
 }

 /**
  * コンストラクタ
  */
 protected AbstractTextFileMatcher(String expectedName,
   InputStream expectedIs) {
  this(expectedName, expectedIs, CHAR_SET);
 }

 /**
  * コンストラクタ
  */
 protected AbstractTextFileMatcher(String expectedName,
   InputStream expectedIs, String charSet) {
  this.expectedName = expectedName;
  this.expectedIs = expectedIs;
  this.charSet = charSet;
 }

 /**
  * 実際のファイルのインプットストリームを取得します。
  *
  * @param actual
  *            実際のファイルの情報
  * @return 実際のファイルのインプットストリーム
  */
 protected abstract InputStream getActualIs(T actual);

 /**
  * 実際のファイルの表示名を取得する。
  *
  * @return 実際のファイルの表示名
  */
 private String getActualName() {
  if (actualName == null) {
   return "actual file";
  } else {
   return actualName;
  }
 }

 /**
  * 比較を行います。
  *
  * @param actual
  *            実際の値
  * @return 比較結果
  */
 @Override
 public boolean matchesSafely(T actual) {
  // インプットストリームを取得
  actualIs = getActualIs(actual);

  BufferedReader expectedReader = null;
  try {
   // 予想されるファイル
   expectedReader = new BufferedReader(new InputStreamReader(
     expectedIs, charSet));
  } catch (UnsupportedEncodingException e) {
   throw new IllegalArgumentException(e);
  }

  BufferedReader actualReader = null;
  try {
   // 実際のファイル
   actualReader = new BufferedReader(new InputStreamReader(actualIs,
     charSet));
  } catch (UnsupportedEncodingException e) {
   throw new IllegalArgumentException(e);
  }

  int lineNum = 0;
  String expectedLine = null;
  String actualLine = null;
  try {
   while ((expectedLine = expectedReader.readLine()) != null
     & (actualLine = actualReader.readLine()) != null) {
    lineNum++;
    if (!expectedLine.equals(actualLine)) {
     // 1行分の内容が一致しない
     unmatchedLineNums.add(Integer.valueOf(lineNum));
     unmatchedExpectedLines.add(expectedLine);
     unmatchedActualLines.add(actualLine);
    }
   }
   if (expectedLine != null || actualLine != null) {
    // 行数が一致しない
    lineNum++;
    unmatchedLineNums.add(Integer.valueOf(lineNum));
    unmatchedExpectedLines.add(expectedLine);
    unmatchedActualLines.add(actualLine);
   }
  } catch (IOException e) {
   // どうにもならないので例外
   throw new IllegalStateException(e);
  } finally {
   try {
    expectedReader.close();
   } catch (IOException e) {
    // 比較は終わっているので握りつぶす
   }
   try {
    actualReader.close();
   } catch (IOException e) {
    // 比較は終わっているので握りつぶす
   }
  }
  return unmatchedLineNums.isEmpty();
 }

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

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

 /**
  * エラーの詳細を設定します。
  *
  * @param description
  *            設定先
  * @param name
  *            表示名
  * @param lines
  *            エラーになった行の内容
  * @param lineNums
  *            エラーになった行番号
  */
 private void setUpDescription(Description description, String name,
   List<String> lines, List<Integer> lineNums) {
  description.appendText(name + " = ");
  if (lineNums.isEmpty()) {
   description.appendText("想定外のエラー");
  }
  for (int i = 0; i < lineNums.size(); i++) {
   if (i != 0) {
    description.appendText(", ");
   }
   description.appendText(lineNums.get(i) + ":");
   description.appendValue(lines.get(i));
  }
  return;
 }

}



0 件のコメント:

コメントを投稿