Seasar DI Container with AOP

目次

S2Unitを使ったテストクラスの作成方法

テストクラスは、org.seasar.extension.unit.S2TestCaseを継承して作成します。またS2TestCaseはJUnitを拡張したクラスなので、テストメソッド等の使用方法はJUnitと同様です。

S2Unitの機能

S2Containerを使った開発のテストを簡単に行えるように以下の機能があります。

・S2Containerの自動生成
S2Containerを生成する場合、テストメソッド(testXxx)ごとに自動的にS2Containerを作成します。
S2ContainerFactory.create(PATH)と記述してS2Containerを生成する必要がありません。
・register(),getComponent(),include()メソッド
S2Containerに対するregister(),getComponent(),include()メソッドが用意されています。
・include時のPATHの省略
include()するPATHがテストクラスと同じパッケージにある場合は、パッケージ部分のパスは省略できます。
例えばaaa.bbb.CccTestクラスがaaa/bbb/hoge.diconをinclude()する場合、include("hoge.dicon")と記述することができます。
・変数の自動セット
TestCaseのstaticでなくfinalでもないフィールドが存在し、その名前からアンダースコア(_)を除いた名前のコンポーネントがコンテナに存在すれば自動的にセットされます。
TestCaseのフィールドにインターフェースの変数が定義されていればS2Containerから取り出して自動的にセットされます。テストメソッドが終わると自動セットされた値は自動的にクリア(nullをセット)されます。
・setUpXxx(),tearDownXxx()メソッド
テストメソッド(testXxx)に対応するsetUpXxx(),tearDownXxx()を定義しておくと、setUp()の後、tearDown()の前に自動的に呼び出されます。
個別のテストメソッドごとの初期化・終了処理を簡単に行えるようになります。

データベースに対するテストを行うために以下の機能が用意されています。

・トランザクション制御
include("j2ee.dicon")をしておき、テストメソッド名の最後にTxをつける(testXxxTx)ことにより、テストメソッドの直前にトランザクションを開始し、テストメソッドの直後にトランザクションをロールバックするようになるので、データベースに関するテストを行った場合のクリー ンアップの処理が不要になります。
・assertEquals()メソッド
assertEquals()で予想されるDataSetの結果に対して、Map、MapのList、Bean、BeanのListと比較することができます。
・readXls()メソッド
DataSet expected = readXls("検証用.xls")のようにしてDataSetに検証用のExcelデータを読み込むことができます。
検証用のExcelデータのPATHがテストクラスと同じパッケージにある場合PATHを省略できます。
検証用のExcelデータを作成する場合はExcelファイルの作成を参照してください。
・reload()メソッド
reload(DataSet)を使い、データの中身をプライマリーキーでリロードして新しいDataSetを取得できます。更新後の予想される結果をExcelで定義しておき、以下のようにして簡単に更新のテストができます。
DataSet expected = readXls("検証用.xls");
assertEquals(expected, reload(expected);
・readXlsWriteDb(),readXlsAllReplaceDb()メソッド
readXlsWriteDb("検証用.xls")又はreadXlsAllReplaceDb("検証用.xls")のようにして検証用のExcelデータをデータベースに格納します。検証用のExcelファイルがテストクラスと同じパッケージにある場合は、パッケージのパスを省略できます。
通常、readXlsWriteDb()、readXlsAllReplaceDb()はテスト後にロールバックしてデータが元に戻るようにtestXxxTx()の最初に実行します。また、これらのメソッドはシートの定義の逆順に削除した後にデータを挿入します。
readXlsAllReplaceDb()を使う場合、外部キー制約に引っかからないように、データのないシートを用意する必要があります。
例えば、テーブルAの外部キーでテーブルBを参照している場合、テーブルAのデータしか使わない場合でも、テーブルB用にシート名だけのシートを用意する必要があります。シートの定義順は、テーブルA、テーブルBの順になります。

Excelファイルの作成

検証用のExcelファイルを作成するには、以下のように設定します。作成したExcelファイルはテストクラスと同じ場所に配置するのが一般的です。

1.シート名
テーブル名を記述します。
2.シートの1行目
カラム名を記述します。

N:1のマッピングもベースとなるシートに「カラム名_関連番号」の名前で記述します。
3.シートの2行目以降
データを記述します。

データベースから検証用のExcelファイルを作成することが出来るように以下のクラスが用意されています。

  • データベースの内容をDataSetに読み込むクラス
  • DataSetをExcelファイルに出力するクラス

データベースの内容をDataSetに読み込むクラス

クラス名
org.seasar.extension.dataset.impl.SqlReader
メソッド詳細
public void addTable(String tableName)
説明:指定されたテーブル名のすべてのデータをDataSetに読み込みます。
第1引数:取り出すデータのテーブル名を指定します。
public void addTable(String tableName, String condition)
説明:指定された条件で指定されたテーブルのデータをDataSetに読み込みます。
第1引数:取り出すデータのテーブル名を指定します。
第2引数:取り出す条件を指定します。
public void addSql(String sql, String tableName)
説明:指定されたテーブルを指定されたSelect文の条件でデータをDataSetに読み込みます。
第1引数:テーブルからデータを取り出すためのSelect文を指定します。
第2引数:取り出すデータのテーブル名を指定します。
public DataSet read()
説明:読み込んだDateSetを返します。
戻り値:addTable()メソッドまたはaddSql()メソッドで読み込んだDateSetを返します。

DataSetをExcelファイルに出力するクラス

クラス名
org.seasar.extension.dataset.impl.XlsWriter
メソッド詳細
(コンストラクタ)public XlsWriter(String path)
説明:コンストラクタでファイルのパスを指定します。
第1引数:出力するファイルパスを指定します。
public void write(DataSet dataSet)
説明:指定されたDataSetをコンストラクタで指定したファイルパスのExcelファイルに出力します。
第1引数:出力するDataSetを指定します。

モックインターセプタ

モックを作成するための設定

インターフェースのモックを設定するには、org.seasar.framework.aop.interceptors.MockInterceptorを使用します。MockInterceptorはS2AOPで提供されているInterceptorです。MockInterceptorの以下のメソッドを使用してインターフェースのモックの設定、インターフェースのメソッドの呼び出し、引数の確認が出来ます。

public void setReturnValue(String methodName, Object returnValue)
概要:インターフェースのメソッドに戻り値をセットする場合に使用します。
第1引数:インターフェースのメソッド名を指定します。
第2引数:第1引数で指定したメソッドの返す値を指定します。
public void setReturnValue(Object returnValue)
概要:インターフェースのすべてのメソッドに戻り値をセットする場合に使用します。
第1引数:返す値を指定します。この場合、どんなメソッドが呼ばれてもその値を返します。通常、1つのメソッドしか呼び出さないことが分かっている場合に使います。
public boolean isInvoked(String methodName)
概要:インターフェースのメソッドが呼ばれたかどうかチェックする場合に使用します。
第1引数:呼び出されたどうか確認したいメソッド名を指定します。
戻り値:booleanでメソッドが呼び出されたかどうかを返します。
public Object[] getArgs(String methodName)
概要:インターフェースのメソッドの引数の値を取得したい場合に使用します。
第1引数:呼び出されたソッドの引数の値を知りたい場合のメソッド名を指定します。
戻り値:Object[]で呼び出されたメソッドの引数の値を返します。

モックの使用方法

インターフェースのモックを作成するには、設定したMockInterceptorをアスペクトして使用します。例えば以下のようなインターフェースがあったとします。

public interface Hello {
    public String greeting();
    public String echo(String str);
}

このインターフェースのモックを作成する仕様として、greeting()メソッドが呼び出されたときは、"Hello"を返し、echo()メソッドが呼び出されたときは"Hoge"を返すこととする場合、以下のように作成します。

MockInterceptor mi = new MockInterceptor();
mi.setReturnValue("greeting", "Hello");
mi.setReturnValue("echo", "Hoge");
Hello hello = mi.createProxy(Hello.class);

上記のコードをコンポーネント定義で書くと次のようになります。

<component class="examples.aop.Hello">
    <aspect>
        <component class="org.seasar.framework.aop.interceptors.MockInterceptor">
            <initMethod name="setReturnValue">
                <arg>"greeting"</arg>
                <arg>"Hello"</arg>
            </initMethod>
            <initMethod name="setReturnValue">
                <arg>"echo"</arg>
                <arg>"Hoge"</arg>
            </initMethod>
        </component>
    </aspect>
</component>

モックの使用例

S2で用意されているMockInterceptorを使ってインターフェースのモックを作成して、メソッドと引数が正しく呼ばれていかS2Unitを使ってテストを作成しましょう。作成するファイルは以下のとおりです。

  • インターフェース(Hello.java)
  • インターフェースにモックの定義を行うdiconファイル(Hello.dicon)
  • 作成したインターフェースのテストを行うクラス(HelloTest.java)
インターフェースの作成
  • 引数が無く、戻り値がStringのメソッド(greeting)を作成します。
  • 引数が1つで、戻り値がStringのメソッド(echo)を作成します。
Hello.java
package examples.aop.mockinterceptor;

public interface Hello {

    public String greeting();

    public String echo(String str);

}
diconファイルの作成
  • モックの仕様はgreeting()メソッドが呼び出されたときは、"Hello"を返し、echo()メソッドが呼び出されたときは"Hoge"を返すことにします。
  • MockInterceptorをコンポーネントに定義します。name属性はhelloMockInterceptorとします。
  • MockInterceptorのsetReturnValue()メソッドに対してメソッド・インジェクションを使ってモックの仕様どおりに引数を指定します。
  • インターフェースをコンポーネントに定義します。aspectタグにコンポーネントに定義したMockInterceptorを指定します。
Hello.dicon
<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component name="mockInterceptor"
               class="org.seasar.framework.aop.interceptors.MockInterceptor">
        <initMethod name="setReturnValue">
            <arg>"greeting"</arg>
            <arg>"Hello"</arg>
        </initMethod>
        <initMethod name="setReturnValue">
            <arg>"echo"</arg>
            <arg>"Hoge"</arg>
        </initMethod>
    </component>
    <component class="examples.aop.mockinterceptor.Hello">
        <aspect>
            mockInterceptor
        </aspect>
    </component>
</components>

テストクラスの作成
  • S2TestCaseを継承します。
  • diconファイルに設定したexamples.aop.Helloとorg.seasar.framework.aop.interceptors.MockInterceptorを自動的にセットするように変数を宣言します。
  • MockInterceptor#isInvoked(String methodName)を使ってメソッドが呼び出されたかどうか確認します。
  • MockInterceptor#getArgs(String methodName)を使ってメソッドの引数の値が正しいか確認します。
HelloTest.java
package examples.aop.mockinterceptor;

import org.seasar.extension.unit.S2TestCase;
import org.seasar.framework.aop.interceptors.MockInterceptor;

public class HelloTest extends S2TestCase{
    //テストクラスと同じパッケージにあるのでパスが省略できる
    private static String PATH = "Hello.dicon";

    //変数の自動セット
    private Hello hello ;

    private MockInterceptor mi ;

    public void testHello() throws Exception{

	//diconファイルでインターフェースのモックが正しく行われているか
	assertEquals("Hello", hello.greeting());
	assertEquals("Hoge", hello.echo("test"));

	hello.echo("Hello");
	//echo()メソッドが呼ばれたかどうか
	assertEquals(true, mi.isInvoked("echo"));

	//echo()メソッドの引数の値が"Hello"かどうか
	assertEquals("Hello", mi.getArgs("echo")[0]);

    }

    protected void setUp() throws Exception {
          //S2Containerに対するinclude()メソッド
	include(PATH);
    }

    protected void tearDown() throws Exception {
    }

    public HelloTest(String arg0) {
	super(arg0);
    }

    public static void main(String[] args) {
	junit.textui.TestRunner.run(HelloTest.class);
    }

}

コンポーネントの組み立てやコンポーネントの取り出しといった作業はS2TestCaseが自動的に行うので、テストメソッドは必要なことにだけに集中できます。

実行結果

"OK (テスト数 test)"と出ていればassertEquals()メソッドで予想した通り結果が得られています。

.
Time: 1.643

OK (1 test)