DIConTestCase

JXUnitのコンセプトをまねて、「XMLの1ファイルが1テストケースになる」という形でDICon用のテスティングフレームワークを作ってみた。

テスト対象コンポーネント

テスト対象になるコンポーネントは以下の通り。


package sample.test.component.impl;

import java.util.Calendar;
import java.util.Date;

import sample.test.component.BusinessLogic;
import sample.test.value.Database;
import sample.test.value.DateInfo;
import sample.test.value.Sysdate;
import sample.test.value.impl.DateInfoImpl;

public class BusinessLogicImpl implements BusinessLogic {

private Sysdate sysdate = null;

private Database database = null;

public BusinessLogicImpl() {
}

public BusinessLogicImpl(Sysdate sysdate, Database database) {
this.sysdate = sysdate;
this.database = database;
}

public void setSysdate(Sysdate sysdate) {
this.sysdate = sysdate;
}

public void setDatabase(Database database) {
this.database = database;
}

public DateInfo execute(int day) {
// 現在日時を取得。
Date now = sysdate.getNow();

// カレンダーのインスタンスを取得。
Calendar cal = Calendar.getInstance();

// カレンダーに現在日時をセット。
cal.setTime(now);

// 現在日時に渡された日数を足す。
cal.add(Calendar.DATE, day);
Date date = cal.getTime();

// 新しい日付でデータベースを更新。
database.update(date);

// 日付情報を作成。
DateInfo dinfo = new DateInfoImpl(date);

// 日付情報を返す。
return dinfo;
}

処理内容はコメントの通り。
セッター・インジェクション(Type2)、コンストラクタ・インジェクション(Type3)の両方に対応。

I/Fとなるオブジェクト

コンポーネントとのI/F(コンポーネントに注入、メソッドの引数、戻り値)として使われるオブジェクトは次のとおり。それぞれインターフェースとそれを実装するクラスがある。

class DatabaseImpl implements Database
データベースをあらわすオブジェクト(のつもり)。


package sample.test.value.impl;

import java.util.Date;

import sample.test.value.Database;

public class DatabaseImpl implements Database {

private Date updateDate = null;

public Date getUpdateDate() {
return updateDate;
}

public void setUpdateDate(Date updateDate) {
this.updateDate = updateDate;
}

public void update(Date date) {
this.updateDate = date;
}

}

class SysdateImpl implements Sysdate
システム日付をあらわすオブジェクト(のつもり)。


package sample.test.value.impl;

import java.util.Date;

import sample.test.value.Sysdate;

public class SysdateImpl implements Sysdate {

private Date date;

public SysdateImpl(Date date) {
this.date = date;
}

public Date getNow() {
return date;
}

}

class DateInfoImpl implements DateInfo
日付をあらわすオブジェクト(のつもり)。


package sample.test.value.impl;

import java.text.SimpleDateFormat;
import java.util.Date;

import sample.test.value.DateInfo;

public class DateInfoImpl implements DateInfo {

private String year = null;

private String month = null;

private String day = null;

public DateInfoImpl() {
}

public DateInfoImpl(Date date) {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
String dateStr = format.format(date);
year = dateStr.substring(0, 4);
month = dateStr.substring(4, 6);
day = dateStr.substring(6, 8);
}

public String getDay() {
return day;
}

public void setDay(String day) {
this.day = day;
}

public String getMonth() {
return month;
}

public void setMonth(String month) {
this.month = month;
}

public String getYear() {
return year;
}

public void setYear(String year) {
this.year = year;
}

}

テストケース

テストケースは以下のようにXMLファイルで記述する。
通常のテストクラスの1テストメソッドが1ファイルに対応する。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dicontestcase>
<dicontestcase>

  <!-- テスト名を記述 ※未定義の場合はファイル名(拡張子を除く)をテスト名とする -->
  <name>サンプルテスト 失敗</name>

  <!-- テストの説明 -->
  <description>
    現在日時を取得して 2日 足す。
  </description>

  <!-- テストするコンポーネント -->
  <target>sample.test.component.impl.BusinessLogicImpl</target>

  <!-- 入力する値 -->
  <input>
    <!-- 実行するメソッド -->
    <execute name="execute">
      <arg value="2" type="int"/>
    </execute>

    <!-- コンポーネントの状態 -->
    <component>
      <database>
        <bean type="sample.test.value.impl.DatabaseImpl">
          <updateDate value="${null}"/>
        </bean>
      </database>
      <sysdate>
        <bean type="sample.test.value.impl.SysdateImpl">
          <constructor>
            <arg value="${2004-12-23 22:11:33}"/>
          </constructor>
        </bean>
      </sysdate>
    </component>
  </input>

  <!-- 期待する値 -->
  <expected>
    <!-- メソッドの戻り値 -->
    <return>
      <bean type="sample.test.value.impl.DateInfoImpl">
        <year value="2004"/>
        <month value="12"/>
        <day value="26"/>
               <!-- ~~ 間違い -->
      </bean>
    </return>

    <!-- コンポーネントの状態 -->
    <component>
      <database type="sample.test.value.Database">
        <bean type="sample.test.value.impl.DatabaseImpl">
          <updateDate value="${2004-12-25 22:11:33}"/>
        </bean>
      </database>
      <sysdate type="sample.test.value.Sysdate">
        <bean type="sample.test.value.impl.SysdateImpl">
          <constructor>
            <arg value="${2004-12-23 22:11:33}"/>
          </constructor>
        </bean>
      </sysdate>
    </component>
  </expected>

</dicontestcase>

入力するオブジェクト(注入するオブジェクト、メソッドの引数)を指定して、メソッドを実行。
実行後、テストの戻り値と注入したオブジェクトの状態を再帰的に精査する。

テストの実行

以下のように設定ファイルで、XMLファイルを置いたディレクトリを指定する。


bodybuilder.test.root.dirs=C:\\eclipse\\workspace\\bodybuilder\\sample\\xml\\testcase

クラス XMLTestRunner をJUnitテストクラスとして実行すると、指定されたディレクトリから再帰的にXMLファイルを探して、テストを実施する。


Eclipseからのテスト実行結果はこんな感じ。

その他

フレームワークとサンプルを含むEclipseの一式はここ
JUnit、Commons BeanUtils/Logging、JDOM、Xercesのライブラリが必要。

大量に実施すると、多分、Out of Memoryになると思う。

TODO

  • ソースにコメントを入れる。
  • サーブレットAPIへの対応。
    • XMLServletTestCaseの作成。
  • MockRunner、StrutsTestCaseへの対応。
    • XMLStrutsTestCaseの作成。
  • 精査しない(できない)値への対応。
  • ロギングの実装。
  • メモリリーク対策。
  • EasyMockへの対応(モックなしでもテストできるようにする)
  • DTDの作成