ひよっこ。

I want to…

Posts Tagged ‘JUnit’

hamcrestで年月日比較がしたくなったのでDateMatcherを作ってみた

Posted by hikaruworld : 2010 10月 21

とりあえず、車輪の再発明な気がします。
どっかにきっとあると思うんだ。

以下、本題です。

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);
    }
}

良ければ使ってみてください。

Posted in program | タグ: , , , | Leave a Comment »

Emma+Antをofflineモードで実行する

Posted by hikaruworld : 2009 5月 3

EMMAとは

Javaのカバレッジ測定ツール。On-the-flyモードとofflineモードの二つを持つ(詳細は後述)。
ダウンロードは以下から。
今回試したバージョンは2.0系
Eclipseプラグインとして、EclEmmaというプラグインも公開されている。

On-the-FlyモードとOfflineモード

簡単に言うと、OnTheFlyはファイルそのまま実行可能で、
Offlineモードは一旦既存のコードにEmmaのカバレッジ用のコードを埋め込んだファイルを吐き出した後に再実行が必要。
詳細は公式サイトの説明(英語)を見るべし。(概要はイントロに書いてある。)

EMMA+JUnitをAntで実行するためには

EMMA自体はJUnit自体に特別に対応しているわけではないので、
JUnit実行時にJUnitのパラメータであるjvmargを利用して引数を渡す必要がある。

なぜ、JUnitに特別対応しないかに関しては、FAQの2.3. Does EMMA integrate with {JUnit, Cactus, …}?でコメントされている。

また、上述したOn-the-FlyモードはJUnitをAntで実行する際には対応していない模様(英語なので自信ないけど)。

構成

テストを実行するプロジェクトの構成は以下の通り。

プロジェクト構成

EmmaSample/
|-- build.xml					--- buildで使用されるAntファイル(コンパイル→EMMAビルド→Junit実行→レポート出力)
|-- lib
|   |-- emma.jar				--- emmaのjarファイル
|   |-- emma_ant.jar			--- emmaをAntで利用する際のjarファイル
|   `-- junit-4.6.jar			--- Junitを実行するのに必要なライブラリ
|-- result
|   `-- TEST-TargetTest.xml		--- JUnitの結果出力先
|-- src
|   `-- Target.java				--- テスト対象ファイル
|-- test
|   `-- TargetTest.java			--- テストファイル(テストコードが書かれている)
`-- work						--- 作業用ディレクトリ
    |-- classes
    |   `-- Target.class		--- コンパイル済みソースファイル(javacで単純にコンパイルされたもの)
    |-- convert
    |   |-- classes			
    |   |   `-- Target.class	--- EMMAが組み込まれたclassファイル(容量が通常のjavacでコンパイルされたものに比べて2倍程度になる)
    |   `-- lib
    |-- emma
    |   |-- coverage.emma		--- コマンド実行後(Mainクラスや)に出力されるメタデータファイル(おそらく実行後にふんだ行を保持しているデータのはず)
    |   `-- metadata.emma		--- Antタスクによって生成されるメタデータファイル(このクラスを実行パスに通す事で、実行後にcoverage.emmaの実行後データを出力するものと思われる。)
    |-- report
    |   |-- _files
    |   |   |-- 0.html
    |   |   `-- 1.html
    |   |-- coverage.html		--- html形式で出力されたカバレッジレポート(./_fileはリンク先)
    |   `-- coverage.xml		--- xml形式で出力されたカバレッジレポート。人間では読みづらいのでHudson先生などに翻訳してもらう。
    `-- test
        `-- TargetTest.class	--- コンパイル済みテストファイル

テスト対象となるクラス

public class Target {

	public int cal(int i, int j) {
		return i + j;
	}
}

テストクラス

import org.junit.BeforeClass;
import org.junit.Test;

import static org.junit.Assert.*;

public class TargetTest {
	private static Target target;
	
	@BeforeClass
	public static void init() {
		target = new Target();
	}
	@Test
	public void calc1() {
		int result = target.cal(1, 2);
		 assertEquals(3, result);
	}
}

ビルドファイル

どこを参照するかで結構混乱するので注意。正確にパスを通していないと、メタデータが出力されない。

<?xml version="1.0" encoding="UTF-8"?>
<!-- ====================================================================== 
     2009/04/14 20:13:26                                                        

     emma-build    
     EMMAのJUnitを用いたビルドを行う。
                   
     morizou                                                                
     ====================================================================== -->
<project name="emma-build" default="emma-report" basedir=".">
    <description>
            EMMAのビルドを行う。
    </description>
	
	<!-- EMMA関連ビルドプロパティ -->
	<property name="emma.dir" value="${basedir}/lib" />
	<path id="emma.lib" >
		<pathelement location="${emma.dir}/emma.jar" />
		<pathelement location="${emma.dir}/emma_ant.jar" />
	</path>

	<!-- 新規タスク(つまり<emma>)を有効化 -->
	<taskdef resource="emma_ant.properties" classpathref="emma.lib" />

	<!-- emmaを用いて実行するかのプロパティ -->
	<property name="emma.enable" value="true"/>

	<!-- JUnitへパスを通す -->
	<property name="JUNIT_PATH" value="${basedir}/lib/junit-4.6.jar"/>

	<!-- 各種プロパティを定義 -->
	<property name="src.target" value="${basedir}/src" description="ソースのディレクトリ"/>
	<property name="test.target" value="${basedir}/test" description="テストソースのディレクトリ"/>
	<property name="src.output" value="${basedir}/work/classes" description="ソースのコンパイル結果出力先"/>
	<property name="test.output" value="${basedir}/work/test" description="テストソースのコンパイル出力先"/>
	<property name="convert.output" value="${basedir}/work/convert" description="emma組み込み後のclassファイル"/>
	<property name="emma.output" value="${basedir}/work/emma" description=".emmaメタデータの出力先"/>
	<property name="report.output" value="${basedir}/work/report" description="emmaのレポート出力先"/>


    <!-- ================================= 
          target: compile ソースのコンパイルを行う。             
         ================================= -->
    <target name="compile" description="ソースのコンパイルを行う。">
    	<javac srcdir="${src.target}" 
			destdir="${src.output}"
		    debug="on"
    		description="ソースのコンパイル"	>
    		<classpath>
    		</classpath>
    	</javac>
    	<javac srcdir="${test.target}" 
			destdir="${test.output}"
		    debug="on"
    		description="テストソースのコンパイル">
    		<classpath>
    			<pathelement location="${src.output}"/>
    			<path path="${JUNIT_PATH}" />
    		</classpath>
    	</javac>
    </target>

    <!-- ================================= 
          target: emma emmaをクラスファイルに組み込んだ形でコンパイルを行い、メタデータファイルを出力する。
         ================================= -->
    <target name="emma" depends="compile">
        <emma enabled="${emma.enable}">
	    <instr instrpath="${src.output}"
	            destdir="${convert.output}"
		    metadatafile="${emma.output}/metadata.emma"
		    merge="yes"
		    mode="fullcopy">
            </instr>
        </emma>
    </target>
	
    <!-- ================================= 
          target: junit junitの実行
         ================================= -->
    <target name="junit" depends="emma">
    	<junit printsummary="on" fork="yes">
                        <!-- jvmargを利用して、emmaの設定を行う -->
			<jvmarg value="-Demma.coverage.out.file=${emma.output}/coverage.emma" />
			<jvmarg value="-Demma.coverage.out.merge=true" />
            <jvmarg value="-Demma.verbosity.level=quiet" />
            <jvmarg value="-Dcoverage.enabled=true" />
    		<classpath>
    			<pathelement location="${convert.output}/classes"/>
    			<pathelement location="${test.output}"/>
    			<pathelement location="${emma.output}"/>
    			<path refid="emma.lib" />
    			<path path="${JUNIT_PATH}" />
    		</classpath>
    		<formatter type="xml"/>
    		<batchtest fork="true" todir="${basedir}/result">
    			<fileset dir="${test.target}">
    				<include name="**/TargetTest.java"/>
    			</fileset>
    		</batchtest>
    	</junit>
    </target>

	<target name="emma-report" depends="junit">
	    <!-- 計測結果のレポートの出力 -->
	    <emma enabled="true">
	        <property name="report.html.out.encoding" value="UTF-8" />
	        <report>
	            <sourcepath>
	                <pathelement location="${src.target}" />
	            </sourcepath>
	            <fileset dir="${emma.output}">
	                <include name="*.emma" />
	            </fileset>
	            <xml outfile="${report.output}/coverage.xml" />
	            <html outfile="${report.output}/coverage.html" />
	        </report>
	    </emma>
	</target>
</project>

レポートの参照

HTMLでの出力レポートも行単位で、GUIで参照できるので結構便利。
Emma-report-html
XMLに関しても、Hudsonなどを利用してレポートをだすと便利に使える。
画像はこっちにはないので省略。。。

余談

そんなに頻繁にアップデートされていない感じがちょっと気になる。

参考サイト

Posted in program | タグ: , , , | Leave a Comment »

Spring1.2系でautoProxyが有効な場合にJUnit4を使ったControllerのテスト

Posted by hikaruworld : 2009 2月 25

久々にEasyMockやらJUnitやら使ってSpringのSimpleController部分のテストしていたが、
色々ど忘れ+はまったのでメモしておく。
(主としてど忘れが多い。)

ちなみにEasyMockに関する記述は省略していて、主としてSpringの設定周りが主。
この件に関しては、以前のエントリを参考のこと。

Controller部分Mock生成に関しては以下のソースが参考になった。
GoogleCode thick4

テスト自体は、ありがたいことにSpringのライブラリからMockオブジェクトが提供されているのでそれを流用する。
Servlet周りのMockオブジェクトはspring-mock.jarのorg.springframework.mock.webパッケージ中に各種そろっている。

  • MockFilterConfig
  • MockHttpServletRequest
  • MockHttpServletResponse
  • MockHttpSession
  • MockPageContext
  • etc…

但し、springのバージョンが古いせいか、一部のMockオブジェクトがないorz…。
ので、Spring2.0系のソースからちょろまかして、流用することにした。
流用したのは以下のMockオブジェクト。

  • MockMultipartFile
  • MockFileItem
  • MockMultipartHttpServletRequest
  • MockFilterChain

基本的にはコピペでOK。
所詮Mockオブジェクトなのでたいしたことはやっていないし。
但し、一部そのまま使えなかった以下の部分は修正した。

MockMultipartFile

/**
 * 実装側が、org.springframework.web.multipart.commons.CommonsMultipartFile
 * にキャストされて実装されていたため、暫定的にCommonsMultipartFileをimplementsするように実装
 */
public class MockMultipartFile extends CommonsMultipartFile {

    // ... 省略

    /**
     * 直接FilterItemをコンストラクタで受け取って処理できるように拡張
     */
    public MockMultipartFile(FileItem fileItem) {
        this(fileItem.getFieldName(),
                fileItem.getName(),
                fileItem.getContentType(),
                fileItem.get(),
                fileItem);
    }

    // ... 省略

    /**
     * throws節が矛盾しているとエラーになるので、IOExceptionを除去
     */
    @Override
    public byte[] getBytes() /*  throws IOException */ {
        return this.content;
    }
}

MockFileItem

存在していなかったので、自作しようと思ってだめもとで検索したら類似のやつを発見したので、
ありがたく参考にさせてもらう。感謝。
View Full Version : Spring 2 Breaks my File Upload Unit test

/**
 * そのまま実行したら、一部エラーになったので修正してある。
 */
public class MockFileItem implements FileItem {

    private String fieldName;
    private String contentType;
    private String name;
    private String value;

    private File writtenFile;
    private boolean deleted;

    public MockFileItem(String fieldName, String contentType, String name, String value) {
        this.fieldName = fieldName;
        this.contentType = contentType;
        this.name = name;
        this.value = value;
        this.writtenFile = new File(this.value);
    }

    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(this.value.getBytes());
    }

    public String getContentType() {
        return this.contentType;
    }

    public String getName() {
        return this.name;
    }

    public boolean isInMemory() {
        return true;
    }

    public long getSize() {
        return this.value.length();
    }

    public byte[] get() {
//            return value.getBytes();
        try {
            return FileUtils.readFileToByteArray(this.writtenFile);
        } catch (IOException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
            return new byte[0];
        }
    }

    public String getString(String encoding)
            throws UnsupportedEncodingException {
        return new String(get(), encoding);
    }

    public String getString() {
        return this.value;
    }

    public void write(File file) throws Exception {
        this.writtenFile = file;
    }

    public File getWrittenFile() {
        return this.writtenFile;
    }

    public void delete() {
        this.deleted = true;
    }

    public boolean isDeleted() {
        return this.deleted;
    }

    public String getFieldName() {
        return this.fieldName;
    }

    public void setFieldName(String s) {
        this.fieldName = s;
    }

    public boolean isFormField() {
        return (this.name == null);
    }

    public void setFormField(boolean b) {
        throw new UnsupportedOperationException();
    }

    public OutputStream getOutputStream() throws IOException {
        throw new UnsupportedOperationException();
    }
}

– MockMultipartHttpServletRequest
– MockFilterChain
これらはそのままコピーでたぶんOK。

以下テストケース実装。

テストクラスの起動タイミングでアプリケーション起動とみなし、@BeforeClassを使って、
ContextLoaderやServletContext、Bean定義ファイルの読み込みを行っている。
また、各メソッドレベル(@Test)の実行を一回のリクエストとして、
HttpServletRequest,HttpServletResponseを生成している。

/**
 * テストクラス
 */
public class SampleControllerTest {
    /** テスト対象Controller */
    private static SampleController controller;

    private static ApplicationContext applicationContext;
    private static MockServletContext servletContext;

    /**
     * テストクラス前実行クラス
     */
    @BeforeClass
    public static void init() {
        // Mock用のContextを生成
        ContextLoader contextLoader = new ContextLoader();
        servletContext = new MockServletContext();

        String configLocationParam = StringUtils.arrayToDelimitedString(new String[]{
              "/WEB-INF/spring/validator.xml",
              "application-servlet.xml",
        }, ",");
        servletContext.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, configLocationParam);
        ConfigurableWebApplicationContext context = (ConfigurableWebApplicationContext)
        contextLoader.initWebApplicationContext(servletContext);

        applicationContext = context;

        controller = (SampleController) applicationContext.getBean("sampleController");
    }
    /**
     * 実際のテストクラス
     */
    @Test
    public void ファイルアップロードテストSample {
        try {
        	  /** FileUploadで用いられるMultipartHttpServletRequestのMockを生成 */
            MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
            request.setRequestURI("/sampe.html");
            request.setMethod("POST");

            MockFileItem fileItem = new MockFileItem(
       	          "file",
       	          "application/vnd.ms-excel;",
       	          fileName,
    	               "sample.xls");
            request.addFile(new MockMultipartFile(fileItem));

            MockHttpServletResponse response = new MockHttpServletResponse()

            // リクエストの実行
            ModelAndView mv = controller.handleRequest(request, response);
            // 実行結果はresultとキーでMapに格納されているので取得
            String mes = (String) mv.getModel().get("result");
            assertEquals("OK", mes);
        } catch (Exception e) {
            e.printStackTrace();
            fail();
        }
    }
}

注意点

Springのautoproxyを有効にしている状態で、Bean定義ファイルの読み込みを行わせると、以下のようなエラーになる。

org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘hogehoge’ defined in ServletContext resource [/WEB-INF/application-servlet.xml]: Error setting property values; nested exception is org.springframework.beans.PropertyAccessExceptionsException: PropertyAccessExceptionsException (1 errors); nested propertyAccessExceptions are: [org.springframework.beans.TypeMismatchException: Failed to convert property value of type [$Proxy8] to required type [hoge.hoge.hoge] for property ‘piyo’]
PropertyAccessExceptionsException (1 errors)
org.springframework.beans.TypeMismatchException: Failed to convert property value of type [$Proxy8] to required type [hoge.hoge.hoge] for property ‘piyo’

なんだか知らないがautoProxyを有効にしているとだめらしい。
参考:マキノ式ブログ
仕方ないのでAOPの設定部分の、定義ファイルを分離してテスト時には読み込まないように対応したが。

話はそれるが、テストケースのメソッドレベルで日本語が使えるのは非常にありがたいなと思う今日この頃である。

Posted in program | タグ: , , , | Leave a Comment »