S2では、コンテナを使った開発のテストを楽しくおこなえるようにテスティングフレームワークが組み込まれています。JUnitを拡張しています。主な機能は以下のとおりです。
ここでは、トランザクションの自動制御をテストするRequiredInterceptorTestを見てみましょう。
package test.org.seasar.extension.tx; import javax.transaction.SystemException; public interface TxBean { public boolean hasTransaction() throws SystemException; }
package test.org.seasar.extension.tx; import javax.transaction.Status; import javax.transaction.SystemException; import javax.transaction.TransactionManager; public class TxBeanImpl implements TxBean { private TransactionManager tm_; public TxBeanImpl(TransactionManager tm) { tm_ = tm; } public boolean hasTransaction() throws SystemException { System.out.println(tm_.getTransaction()); return tm_.getStatus() != Status.STATUS_NO_TRANSACTION; } }
<components> <component class="org.seasar.extension.jta.TransactionManagerImpl"/> <component name="requiredTx" class="org.seasar.extension.tx.RequiredInterceptor"/> <component class="test.org.seasar.extension.tx.TxBeanImpl"> <aspect>requiredTx</aspect> </component></components>
package test.org.seasar.extension.tx; import javax.transaction.Status; import javax.transaction.TransactionManager; import junit.framework.Test; import junit.framework.TestSuite; import org.seasar.extension.unit.S2TestCase; public class RequiredInterceptorTest extends S2TestCase { private static final String PATH = "RequiredInterceptorTest.dicon"; private TxBean txBean_; private TransactionManager tm_; public RequiredInterceptorTest(String name) { super(name); } public void testInvoke() throws Exception { assertEquals("1", true, txBean_.hasTransaction()); assertEquals("2", Status.STATUS_NO_TRANSACTION, tm_.getStatus()); } public void testInvoke2() throws Exception { tm_.begin(); assertEquals("1", true, txBean_.hasTransaction()); assertEquals("2", Status.STATUS_ACTIVE, tm_.getStatus()); tm_.commit(); } protected void setUp() throws Exception { include(PATH); } protected void tearDown() throws Exception { } public static Test suite() { return new TestSuite(RequiredInterceptorTest.class); } public static void main(String[] args) { junit.textui.TestRunner.main( new String[] { RequiredInterceptorTest.class.getName()}); } }
S2では、データベースに対するテストも簡単に行えるような仕組みを用意しています。それではさっそく例を見てみましょう。SQL文を発行するためのフレームワークとしてここでは最も単純なS2JDBCを使います。
今回は、従業員を従業員番号で検索するDAOをサンプルにします。シナリオとして従業員番号9900で検索をかけると、従業員番号9900の従業員テーブルと部署番号99の部署テーブルをジョインして返す想定とします。このケースをテストするためには、検索のための従業員テーブルと部署テーブルのデータと検索した結果を検証するためのデータが必要です。データはExcelで用意します。シート名がテーブル名で、シートの第1行にカラム名を2行目以降にデータを書き込みます。1から手でデータを作成してもいいのですが、ここでは既存のテーブルのデータを利用してテストデータを作成します。セットアップを参照してHSQLDBを起動しておきます。データベースの内容をExcelに書き出すDb2Excelが用意されているのでそれを使います。
<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>
データベースの内容をDataSetに読み込んでくれるのがSqlReaderです。addTable()の最初の引数はテーブル名(シート名)です。2番目の引数は条件になります。
DataSetをExcelに書き出してくれるのがXlsWriterです。コンストラクタでファイルのパスを指定します。パスはEclipseのデフォルト出力フォルダが起点になります。
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(); } } }
S2ContainerからSqlReaderを取り出しread()、XlsWriterを取り出しwrite()するだけで、データベースの内容をExcelに書き出すことができます。なんかダイコン時代のプログラムって感じですね(笑)。Eclipseでtest.examples.unitのパッケージを最新表示させると、getEmployeePrepare.xlsが作成されていることが確認できると思います。getEmployeePrepare.xlsを右クリックしてアプリケーションから開く->システムエディタを選ぶとExcelが起動します。単にダブルクリックするとEclipse上にExcelが表示されてかなり使いにくいので気をつけてください。empシートのEMPNOを9900、ENAMEをSCOTT2に変更します。続いてdeptシートのDEPTNOを99、DNAMEをRESEARCH2に変更します。これで検索用の元データは用意できました。Excelで保存を選び終了させます。つぎにgetEmployeePrepare.xlsを右クリックして最新表示を選びます。これをやることで、Eclipseが変更があったことを認識し、デフォルトの出力フォルダにブックをコピーしてくれます。
次に結果を検証するためのデータを用意します。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.dnameFROM 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>
先ほどと同様な手順でgetEmployeeResult.xlsのempシートのEMPNOを9900、ENAMEをSCOTT2、DEPTNOを99、DNAMEをRESEARCH2に書き換えて保存します。直接作ったほうが早かった気がしますが、SqlReader.addSql()で任意のSelect文を実行できるんだよというデモということで(^^;)。これでテスト用のデータがそろいました。いよいよテストに取り掛かります。
package test.examples.unit; import org.seasar.extension.dataset.DataSet; import org.seasar.extension.dataset.DataTable; import org.seasar.extension.dataset.impl.DataTableImpl; 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("j2ee.dicon"); include("examples/unit/EmployeeDao.dicon"); } public void testGetEmployeeTx() throws Exception { readXlsWriteDb("getEmployeePrepare.xls"); Employee emp = dao_.getEmployee(9900); DataTable table = new DataTableImpl("emp"); table.setupColumns(Employee.class); table.copyFrom(emp); DataSet dataSet = readXls("getEmployeeResult.xls"); assertEquals("1", dataSet.getTable("emp"), table); } public static void main(String[] args) { junit.textui.TestRunner.run(EmployeeDaoImplTest.class); } }
setUp()がapp.diconの役割を担います。testGetEmployeeTxのようにテストメソッド名の最後にTxをつけるとテストメソッドを開始する直前にトランザクションを開始し、テストメソッドが終了した直後にトランザクションがロールバックされます。テストのためにデータベースに格納したデータもすべてロールバックしてもとに戻るためデータのクリーンアップを考える必要がなくなります。
readXlsWriteDb()、readXlsAllReplaceDb()で、テストのために用意したデータをデータベースに格納します。Excelのファイルがテストクラスと同じパッケージにある場合は、パッケージのパスを省略できます。readXlsWriteDb()、readXlsAllReplaceDb()はテスト後にロールバックしてデータが元に戻るようにtestXxxTx()の最初に実行してください。これらのメソッドは、シートの定義の逆順に削除した後にデータを挿入します。readXlsAllReplaceDb()を使う場合、外部キー制約に引っかからないように、データのないシートを用意する必要がある場合があります。例えば、テーブルAの外部キーでテーブルBを参照している場合、テーブルAのデータしか使わない場合でも、テーブルB用にシート名だけのシートを用意する必要があります。シートの定義順は、テーブルA、テーブルBの順になります。
インターフェース型でインスタンス変数(dao_)を宣言しておけば、テストメソッドを開始する前にS2Containerから取り出されて自動的に設定されます。Daoを呼び出して取得したデータは、Excelで用意したデータと結果を比較するためにDataTableに変換します。DataTable.setupColumns(Class)でJavaBeansのメタ情報よりカラムのデータを構築します。後は、DataTable.copyFrom()でDaoを呼び出した結果をDataTableにコピーします。copyFromの引数には、JavaBeans,Map,JavaBeansやMapを要素に持つListを指定することができます。
readXls()で結果検証用のExcelデータを読み込み、Daoの結果と比較します。S2Unitを使えば、楽しく・効率良くテストを行えるということを分かっていただけたと思います。