Seasar DI Container with AOP

テストの概要

S2Containerではコンポーネント同士がインターフェースのみで会話することにより依存関係をなくすということから、 インターフェースの実装が出来上がってからテストを行うことになり開発のボトルネックが発生します。この問題を解決するために、Seasar2ではインターフェースのモックを簡単に作成できます。 実装をモックオブジェクトに置き換えることが可能なため、出来上がった順にテストが出来るので、開発のボトルネックが発生しづらくなります。 またコンテナを使った開発のテストを簡単に行う機能(S2Unit)、データベースに対するテストをExcelファイルで行う機能が提供されています。

S2テストリファレンス

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

インターフェースのモックを設定するには、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>

詳しい使用方法は、Exampleのモックを参照してください。

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を指定します。

詳しい使用方法はExampleのデータベースに対するテストを参照してください。

S2DaoTestCase

S2Daoで実行したSELECT文の結果(Bean or BeanList)を簡単にテストできるようにS2DaoTestCaseクラスが用意されています。

S2DaoTestCaseを使用するにあたり作成すべきファイル

テストを実施するにあたり、テスト対象のDao、JavaBeans、SQLファイル(自動生成の場合は必要ありません)、diconファイル、テストクラス、SELECT文の予想として使用するExcelファイルが必要となります。

Dao、JavaBeans、SQLファイル、diconファイルについてはS2Daoのページを参照してください。

テストクラス

テストクラスは以下のクラスを継承して作成します。

- org.seasar.dao.unit.S2DaoTestCase

また以下の"test"で始まる形式のメソッドを一つ以上実装する必要があります。

- public void testXxx()

Excelファイル

ExcelファイルはDaoが実行したSELECT文の予想結果として使用します。作成したExcelファイルはテストクラスと同じ場所に配置するのが一般的です。

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

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

EMPテーブルを全件検索するSELECT文の結果を予想したExcelのサンプル(getAllEmployeesResult.xls)は以下のようになります。

テスト方法

Daoの実行結果と予想したExcelファイルを比較する手順は以下の通りです。

  1. 以下のメソッドを使用し、作成したExcelを読み込みます。

    - org.seasar.extension.dataset.DataSet S2DaoTestCase#readXls(String path)

    引数にはExcelのパスを設定してください。Excelの情報が戻り値となります。
  2. 次にDaoを実行します。実行方法についてはS2Daoの実行を参照してください。
  3. 以下のメソッドを使用し、作成したExcelとDaoの実行結果をテストします。

    - S2DaoTestCase#assertEquals(String message, DataSet expected, Object actual)

    第1引数は省略可能です。
    第2引数はreadXlsメソッドで読み込んだ結果を指定します。
    第3引数はDaoの実行結果を指定します。ただし、Beanまたは、BeanのListとなるので、Daoのメソッドの戻り値はBeanかListである必要があります。

1~3を実装したサンプルは以下のようになります。

DataSet expected = readXls("getAllEmployeesResult.xls"); /* 手順1 */
List actual = dao_.getAllEmployees(); /* 手順2 */
assertEquals("1", expected, actual); /* 手順3 */

S2DaoTestCaseクラスは、S2TestCaseクラスを継承していますので、S2TestCaseクラスやその他のメソッド等の使用方法については、 S2Unitを使ったテストクラスの作成方法を参照してください。

S2DaoTestCaseの詳しい使用方法はExampleのS2DaoTestCaseのExampleを参照してください。

Example

以下のサンプルを試す場合は、セットアップを参照して環境を作ってから行ってください。
S2DaoTestCaseのExampleに関しては、こちらのセットアップを参照してください。

モック

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)

この演習は、seasar2/src/examples/aop/mockinterceptor以下に用意されています。

データベースに対するテスト

以下のサンプルを動かすためにはHSQLDBを使用しますので、セットアップを参照してHSQLDBを起動させておいてください。ここでは、以下のEMP(従業員)テーブルとDEPT(部署)テーブルを使います。

テーブル:EMP(従業員)
カラム名 論理名 NotNull 主キー
EMPNO 従業員番号 NUMBER
ENAME 従業員名 VARCHAR2

JOB 仕事 VARCHAR2

MGR 上司 NUMBER

HIREDATE 雇用日 DATE

SAL 給料 NUMBER

COMM 手数料 NUMBER

DEPTNO 部署番号 NUMBER


テーブル:DEPT(部署)
カラム名 論理名 NotNull 主キー
DEPTNO 部署番号 NUMBER
DNAME 部署名 VARCHAR2

LOC ロケーション VARCHAR2

VERSIONNO バージョン番号 NUMBER

SQL文を発行するためのフレームワークとして最も単純なS2JDBCを使ってデータベースに対するテストを行います。 テストをする内容は従業員テーブルを従業員番号で検索するDAOの結果について行います。検索を行うDAOのソースはseasar2/src/exapmles/unit/以下に配置してあります。 シナリオとして従業員番号9900で検索をかけると、従業員番号9900の従業員テーブルと部署番号99の部署テーブルをジョインして返す想定とします。

テストを行う為の手順は以下のとおりになります。

  1. テスト用の従業員テーブルと部署テーブルのExcelデータの作成
  2. 結果を検証するためのExcelデータの作成
  3. テストを行うクラスの作成、実行
1.テスト用の従業員テーブルと部署テーブルのExcelデータの作成

Excelのデータを用意するには、シート名がテーブル名で、シートの第1行にカラム名を2行目以降にデータを記述して作成できますが、ここでは既存のテーブルのデータを利用してテストデータを作成します。

テスト用のテーブルデータを作成するためのファイルは以下のとおりです。

  • データベースの内容をExcelに書き出す為のdiconファイル(Db2Excel.dicon)
  • diconファイルに設定した内容をExcelにデータを書き出す実行ファイル(Db2Excel.java)

diconファイルの作成

  • j2ee.diconをインクルードします。
  • SqlReaderクラスのコンポーネント定義をします。1レコードしか必要ないので条件として従業員テーブルは従業員番号を7788で部署テーブルは部署番号を20で検索した結果のデータとします。
  • XlsWriterクラスのコンポーネント定義をします。コンストラクタ・インジェクションで出力するファイルパスを指定します。
Db2Excel.dicon
<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <include path="j2ee.dicon"/>
    <component class="org.seasar.extension.dataset.impl.SqlReader">
        <initMethod>#self.addTable("emp", "empno = 7788")</initMethod>
        <initMethod>#self.addTable("dept", "deptno = 20")</initMethod>
    </component>
    <component class="org.seasar.extension.dataset.impl.XlsWriter" instance="prototype">
        <arg>"../src/test/examples/unit/getEmployeePrepare.xls"</arg>
    </component>
</components>

実行ファイルの作成

  • org.seasar.framework.container.S2Container#create()メソッドの最初の引数に作成したdiconファイル(Db2Excel.dicon)のパスを指定してコンテナを作成します。
  • org.seasar.framework.container.S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(SqlReader.class)を指定して取得します。
  • org.seasar.framework.container.S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(XlsWriter.class)を指定して取得します。
  • コンテナから取得したXlsWriter#write()メソッドの第1引数にSqlReader#read()を指定して実行します。
Db2Excel.java
package test.examples.unit;

import org.seasar.extension.dataset.impl.SqlReader;
import org.seasar.extension.dataset.impl.XlsWriter;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;

public class Db2Excel {

    private static final String PATH =
        "test/examples/unit/Db2Excel.dicon";

    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        container.init();
        try {
            SqlReader reader = (SqlReader)
                container.getComponent(SqlReader.class);
            XlsWriter writer = (XlsWriter)
                container.getComponent(XlsWriter.class);
            writer.write(reader.read());
        } finally {
            container.destroy();
        }
    }
}

実行結果

../src/test/examples/unit/getEmployeePrepare.xlsが以下のように作成されていることを確認してください。

従業員番号9900で検索をかけると、従業員番号9900の従業員テーブルと部署番号99の部署テーブルをジョインして返す想定としているので、empシートのEMPNOを9900とDEPTNOを99、deptシートのDEPTNOを99に書き換えて保存します。

2.結果を検証するためのExcelデータの作成

結果を検証するためのデータを用意します。このデータも手書きで用意できますが先ほどと同じように作成します。結果を検証するデータを作成するためのファイルは以下のとおりです。

  • 先ほど作成したDb2Excel.diconを変更して、結果を検証するためのデータをExcelに書き出すようにするdiconファイル(Db2Excel.dicon)

diconファイルの作成

  • SqlReaderクラスのaddSql()メソッドを使用してSqlReaderクラスのコンポーネントの定義をしなおします。
  • XlsWriterクラスのコンポーネント定義で出力するファイルパスを変更します。
Db2Excel.dicon
<components>
    <include path="j2ee.dicon"/>
    <component class="org.seasar.extension.dataset.impl.SqlReader">
        <initMethod>
        #self.addSql("SELECT e.empno, e.ename, e.deptno, d.dname
        FROM emp e, dept d WHERE empno = 7788 AND e.deptno = d.deptno", "emp")
        </initMethod>
    </component>
    <component class="org.seasar.extension.dataset.impl.XlsWriter"
        instance="prototype">
        <arg>"../src/test/examples/unit/getEmployeeResult.xls"</arg>
    </component>
</components>

先ほど作成した実行ファイル(Db2Excel.java)を実行します。

実行結果

../src/test/examples/unit/getEmployeeResult.xlsが以下のように作成されていることを確認してください。

従業員番号9900で検索をかけると、従業員番号9900の従業員テーブルと部署番号99の部署テーブルをジョインして返す想定としているので、empシートのEMPNOを9900、DEPTNOを99に書き換えて保存します。

3.テストを行うクラスの作成

テスト用の従業員テーブルと部署テーブルのExcelデータと結果を検証するExcelデータが作成できたので、それを使って実際にDaoを呼び出して取得したデータの検証をするテストクラスを作成します。

テストクラスの作成

  • S2TestCaseを継承します。
  • setUp()でdiconファイルをインクルードします。
  • S2Unitのトランザクション制御機能を使ってテスト用の従業員テーブルと部署テーブルのExcelデータをデータベースに格納してもテストメソッドが終了した直後にトランザクションがロールバックされるようにテストメソッド名の最後にTxを付ける。
  • readXlsWriteDb()メソッドを使ってテスト用のExcelデータをデータベースに登録します。
  • readXls()メソッドを使って検証用のExcelデータをDataSetに読み込ませます。
  • assertEquals()メソッドを使って読み込ませたDataSetとDaoを呼び出して取得したデータを比較します。
EmployeeDaoImplTest.java
package test.examples.unit;

import org.seasar.extension.dataset.DataSet;
import org.seasar.extension.unit.S2TestCase;

import examples.unit.Employee;
import examples.unit.EmployeeDao;

public class EmployeeDaoImplTest extends S2TestCase {

    private EmployeeDao dao_;

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

    public void setUp() {
        include("examples/unit/EmployeeDao.dicon");
    }

    public void testGetEmployeeTx() throws Exception {
        readXlsWriteDb("getEmployeePrepare.xls");
        Employee emp = dao_.getEmployee(9900);
        DataSet expected = readXls("getEmployeeResult.xls");
        assertEquals("1", expected, emp);
    }

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

実行結果

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

.DEBUG 2004-10-08 13:10:00,762 [main] トランザクションを開始しました
DEBUG 2004-10-08 13:10:05,379 [main] 物理的なコネクションを取得しました
DEBUG 2004-10-08 13:10:05,469 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-08 13:10:05,990 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-08 13:10:06,140 [main] INSERT INTO emp(EMPNO, ENAME, JOB, MGR, HIREDATE, SAL,
COMM, DEPTNO) VALUES(9900, 'SCOTT2', 'ANALYST', 7566, '1982-12-09 00.00.00', 3000, null, 99)
DEBUG 2004-10-08 13:10:06,140 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-08 13:10:06,901 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-08 13:10:06,911 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-08 13:10:07,151 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-08 13:10:07,151 [main] INSERT INTO dept(DEPTNO, DNAME, LOC) VALUES(99, 'RESEARCH2',
'DALLAS')
DEBUG 2004-10-08 13:10:07,151 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-08 13:10:07,151 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-08 13:10:07,151 [main] SELECT e.empno, e.ename, e.deptno, d.dname FROM emp e, dept d
WHERE e.empno = 9900 AND e.deptno = d.deptno
DEBUG 2004-10-08 13:10:07,151 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-08 13:10:07,312 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-08 13:10:07,392 [main] トランザクションをロールバックしました
DEBUG 2004-10-08 13:10:07,492 [main] 物理的なコネクションを閉じました

Time: 13.87

OK (1 test)

このようにreadXls()で結果検証用のExcelデータを読み込み、Daoの結果と比較することが出来ることが確認できます。また、テストのためにデータベースに格納したデータもすべてロールバックしてもとに戻るため、データのクリーンアップを考える必要がなくなります。

この演習は、seasar2/src/test/examples/unit以下に用意されています。

S2DaoTestCaseのExample

以下のSELECT文のテストを行う演習です。

全件検索をするSELECT文(EmployeeDao_getAllEmployees.sql)
SELECT emp.*, dept.dname dname_0, dept.loc loc_0 FROM emp, dept
WHERE emp.deptno = dept.deptno ORDER BY emp.empno

作成するファイルは以下のとおりです。

  • Dao(EmployeeDao.java)
  • diconファイル(EmployeeDao)
  • Excelファイル(getAllEmployeesResult.xls)
  • テスト実行javaファイル(EmployeeDaoTest.java)

※テスト対象のDao, diconファイルはSQL文を記述する場合のExampleのものを使用しています。

Excelファイルの作成
  • テストするSELECT文の予想結果をExcelファイルに作成します

作成(予想)したExcelファイル(getAllEmployeesResult.xls)は以下のようになります。

テスト実行javaファイルの作成
  • org.seasar.dao.unit.S2DaoTestCaseを継承して作成します。
  • getAllEmployeesメソッドに対するテストメソッド(testGetAllEmployees)を実装します。
  • 作成したExcelファイルを読み込みます。
  • Daoに定義したメソッドを実行します。
  • 実行した結果とExcelファイルをassertEqualsメソッドで比較します。
package test.examples.dao;

import java.util.List;

import org.seasar.dao.unit.S2DaoTestCase;
import org.seasar.extension.dataset.DataSet;

import examples.dao.EmployeeDao;

public class EmployeeDaoTest extends S2DaoTestCase {

    private EmployeeDao employeeDao_;

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

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

    public void setUp() {
        include("examples/dao/EmployeeDao.dicon");
    }

    public void testGetAllEmployees() throws Exception {
        DataSet expected = readXls("getAllEmployeesResult.xls");
        List actual = employeeDao_.getAllEmployees();
        assertEquals("1", expected, actual);
    }
}
実行結果
DEBUG 2004-09-10 10:55:07,447 [main] 物理的なコネクションを取得しました
DEBUG 2004-09-10 10:55:07,447 [main] 論理的なコネクションを取得しました
DEBUG 2004-09-10 10:55:08,384 [main] 論理的なコネクションを閉じました
DEBUG 2004-09-10 10:55:09,181 [main] SELECT emp.*, dept.dname dname_0, dept.loc loc_0 FROM emp, dept
WHERE emp.deptno = dept.deptno ORDER BY emp.empno
DEBUG 2004-09-10 10:55:09,181 [main] 論理的なコネクションを取得しました
DEBUG 2004-09-10 10:55:09,306 [main] 論理的なコネクションを閉じました
DEBUG 2004-09-10 10:55:09,306 [main] 論理的なコネクションを取得しました
DEBUG 2004-09-10 10:55:09,353 [main] 論理的なコネクションを閉じました
DEBUG 2004-09-10 10:55:09,353 [main] 物理的なコネクションを閉じました

Time: 4.844

OK (1 test)

“OK(1 test)”と出力されましたので、予想した結果(Excel)とDaoの実行結果が等しいということになります。
この演習は、s2dao/src/test/examples/dao以下に用意されています。