package org.seasar.extension.unit; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.sql.DataSource; import javax.transaction.TransactionManager; import junit.framework.Assert; import junit.framework.TestCase; import org.seasar.extension.dataset.ColumnType; import org.seasar.extension.dataset.DataReader; import org.seasar.extension.dataset.DataRow; import org.seasar.extension.dataset.DataSet; import org.seasar.extension.dataset.DataTable; import org.seasar.extension.dataset.DataWriter; import org.seasar.extension.dataset.impl.SqlDeleteTableWriter; import org.seasar.extension.dataset.impl.SqlReloadReader; import org.seasar.extension.dataset.impl.SqlReloadTableReader; import org.seasar.extension.dataset.impl.SqlTableReader; import org.seasar.extension.dataset.impl.SqlWriter; import org.seasar.extension.dataset.impl.XlsReader; import org.seasar.extension.dataset.impl.XlsWriter; import org.seasar.extension.dataset.types.ColumnTypes; import org.seasar.extension.jdbc.UpdateHandler; import org.seasar.extension.jdbc.impl.BasicUpdateHandler; import org.seasar.framework.container.ComponentDef; import org.seasar.framework.container.ContainerConstants; import org.seasar.framework.container.S2Container; import org.seasar.framework.container.factory.S2ContainerFactory; import org.seasar.framework.container.impl.S2ContainerImpl; import org.seasar.framework.exception.EmptyRuntimeException; import org.seasar.framework.exception.NoSuchMethodRuntimeException; import org.seasar.framework.util.ClassUtil; import org.seasar.framework.util.ConnectionUtil; import org.seasar.framework.util.DataSourceUtil; import org.seasar.framework.util.FieldUtil; import org.seasar.framework.util.FileOutputStreamUtil; import org.seasar.framework.util.MethodUtil; import org.seasar.framework.util.ResourceUtil; import org.seasar.framework.util.StringUtil; /** *
JUnitのTestCaseを拡張した、S2を使ったテストを楽しくおこなうための * クラスです。
* *各テストメソッドを実行する際には、以下の順番で処理をおこないます。 * テストメソッド名が関係する処理がありますので、ここではtestXxx()という * テストメソッドを実行すると仮定します。
* *setUp()内、またはsetUpXxx()内でinclude("j2ee.dicon")を行い、テスト * メソッド名の最後に"Tx"と付けると、テストメソッドの実行前にトランザク * ションを開始し、テストメソッドの終了後にトランザクションをロールバック * します。DBのレコードを変更するようなテストの場合にこの機能を利用すると、 * クリーンアップ処理が不要になります。
* * @author higa * @see junit.framework.TestCase */ public class S2TestCase extends TestCase { private static final String DATASOURCE_NAME = "j2ee" + ContainerConstants.NS_SEP + "dataSource"; private S2Container container_; private DataSource dataSource_; private Connection connection_; private DatabaseMetaData dbMetaData_; private List bindedFields_; /** *指定された名前でテストケースを作成します。
* * @param name テストケースの名前 */ public S2TestCase(String name) { super(name); } /** *テストケースが保有するS2コンテナを取得します。
* * @return S2コンテナ */ public S2Container getContainer() { return container_; } /** *コンポーネント名を指定してS2コンテナからコンポーネントを取得 * します。
*指定した名前を持つコンポーネントが登録されていない場合はnullを * 返します。
* * @param componentName 取得するコンポーネント名 * @return 指定した名前を持つコンポーネント * @see org.seasar.framework.container.S2Container#getComponent(java.lang.Object) */ public Object getComponent(String componentName) { return container_.getComponent(componentName); } /** *クラスを指定してS2コンテナからコンポーネントを取得します。
*インターフェースを指定した場合はそのインターフェースを実装して * いるコンポーネント、クラスを指定した場合はそのクラスかそのクラスを * 親に持つ子クラスのコンポーネントを取得します。
*指定したクラスのコンポーネントが登録されていない場合はnullを * 返します。
* * @param componentClass 取得するクラス * @return 指定したクラスのコンポーネント * @see org.seasar.framework.container.S2Container#getComponent(java.lang.Object) */ public Object getComponent(Class componentClass) { return container_.getComponent(componentClass); } /** *コンポーネント名を指定してS2コンテナからコンポーネント定義を * 取得します。
* * @param componentName 取得するコンポーネント名 * @return 指定した名前を持つコンポーネント定義 * @see org.seasar.framework.container.S2Container#getComponentDef(java.lang.Object) */ public ComponentDef getComponentDef(String componentName) { return container_.getComponentDef(componentName); } /** *クラスを指定してS2コンテナからコンポーネント定義を取得します。 *
* * @param componentClass 取得するクラス * @return 指定したクラスのコンポーネント定義 * @see org.seasar.framework.container.S2Container#getComponentDef(java.lang.Object) */ public ComponentDef getComponentDef(Class componentClass) { return container_.getComponentDef(componentClass); } /** *クラスをS2コンテナにコンポーネント定義として登録します。
* * @param componentClass コンポーネントのクラス * @see org.seasar.framework.container.S2Container#register(java.lang.Class) */ public void register(Class componentClass) { container_.register(componentClass); } /** *クラスをS2コンテナに名前付きコンポーネント定義として登録 * します。
* * @param componentClass コンポーネントのクラス * @param componentName コンポーネントの名前 * @see org.seasar.framework.container.S2Container#register(java.lang.Class, java.lang.String) */ public void register(Class componentClass, String componentName) { container_.register(componentClass, componentName); } /** *オブジェクトをS2コンテナにコンポーネントとして登録します。
*キーはオブジェクトのクラスになります。
* * @param component コンポーネントとして登録するオブジェクト * @see org.seasar.framework.container.S2Container#register(java.lang.Object) */ public void register(Object component) { container_.register(component); } /** *オブジェクトをS2コンテナに名前付きコンポーネントとして登録 * します。
* * @param component コンポーネントとして登録するオブジェクト * @param componentName コンポーネント名 * @see org.seasar.framework.container.S2Container#register(java.lang.Object, java.lang.String) */ public void register(Object component, String componentName) { container_.register(component, componentName); } /** *S2コンテナにコンポーネント定義を登録します。
* * @param componentDef 登録するコンポーネント定義 * @see org.seasar.framework.container.S2Container#register(org.seasar.framework.container.ComponentDef) */ public void register(ComponentDef componentDef) { container_.register(componentDef); } /** *設定ファイルのパスを指定して子コンテナをincludeします。
*パスはCLASSPATHで指定されているディレクトリをルートとする * 設定ファイルの絶対パスか、ファイル名のみを指定します。
*ファイル名のみの場合、テストケースと同じパッケージにあるもの * とします。
* * @param path 子コンテナの設定ファイルのパス */ public void include(String path) { S2ContainerFactory.include(container_, convertPath(path)); } private String convertPath(String path) { if (ResourceUtil.getResourceNoException(path) != null) { return path; } else { String prefix = getClass().getPackage().getName().replace('.', '/'); return prefix + "/" + path; } } /** *データソースを取得します。
* * @return データソース */ public DataSource getDataSource() { if (dataSource_ == null) { throw new EmptyRuntimeException("dataSource"); } return dataSource_; } /** *データソースからコネクションを取得します。
* * @return データソースから取得したコネクション */ public Connection getConnection() { if (connection_ != null) { return connection_; } connection_ = DataSourceUtil.getConnection(getDataSource()); return connection_; } /** *コネクションのデータベースメタデータを取得します。
* * @return コネクションのデータベースメタデータ */ public DatabaseMetaData getDatabaseMetaData() { if (dbMetaData_ != null) { return dbMetaData_; } dbMetaData_ = ConnectionUtil.getMetaData(getConnection()); return dbMetaData_; } /** *Excelファイルを読み、DataSetを作成します。
*シート名をテーブル名、一行目をカラム名、二行目以降をデータ * として読み込みます。
* *パスはCLASSPATHで指定されているディレクトリをルートとする * 設定ファイルの絶対パスか、ファイル名のみを指定します。ファイル名 * のみの場合、テストケースと同じパッケージにあるものとします。
* * @param path Excelファイルのパス * @return Excelファイルの内容から作成したDataSet * @see org.seasar.extension.dataset.impl.XlsReader#read() */ public DataSet readXls(String path) { DataReader reader = new XlsReader(convertPath(path)); return reader.read(); } /** *DataSetの内容から、Excelファイルを作成します。
*シート名にテーブル名、一行目にカラム名、二行目以降にデータ * を書き込みます。
* *パスはCLASSPATHで指定されているディレクトリをルートとする * 設定ファイルの絶対パスか、ファイル名のみを指定します。ファイル * 名のみの場合、テストケースと同じパッケージにあるものとします。
* * @param path Excelファイルのパス * @param dataSet Excelファイルに書き込む内容のDataSet * @see org.seasar.extension.dataset.impl.XlsWriter#write(DataSet) */ public void writeXls(String path, DataSet dataSet) { File dir = ResourceUtil.getBuildDir(getClass()); File file = new File(dir, convertPath(path)); DataWriter writer = new XlsWriter(FileOutputStreamUtil.create(file)); writer.write(dataSet); } /** *DataSetをDBに書き込みます。
* * @param dataSet データベースに書き込む内容のDataSet * @see org.seasar.extension.dataset.impl.SqlWriter#write(DataSet) */ public void writeDb(DataSet dataSet) { DataWriter writer = new SqlWriter(getDataSource()); writer.write(dataSet); } /** *DBからレコードを読み込み、DataTableを作成します。
* * @param table 読み込むテーブル名 * @return 読み込んだ内容から作成したDataTable * @see org.seasar.extension.dataset.impl.SqlTableReader#read() */ public DataTable readDbByTable(String table) { return readDbByTable(table, null); } /** *DBからレコードを読み込み、DataTableを作成します。
*読み込むレコードはconditionの条件を満たすレコードです。 * conditionには" WHERE "より後ろをセットしてください。
* * @param table 読み込むテーブル名 * @param condition 条件句(WHEREの後ろ) * @return 読み込んだ内容から作成したDataTable * @see org.seasar.extension.dataset.impl.SqlTableReader#read() */ public DataTable readDbByTable(String table, String condition) { SqlTableReader reader = new SqlTableReader(getDataSource()); reader.setTable(table, condition); return reader.read(); } /** *DBからSQL文の実行結果を取得し、DataTableを作成します。
*作成したDataTableのテーブル名はtableNameになります。
* * @param sql 実行するSQL文 * @param tableName 作成するDataTableのテーブル名 * @return 読み出した内容のDataTable * @see org.seasar.extension.dataset.impl.SqlTableReader#read() */ public DataTable readDbBySql(String sql, String tableName) { SqlTableReader reader = new SqlTableReader(getDataSource()); reader.setSql(sql, tableName); return reader.read(); } /** *Excelファイルを読み込み、DBに書き込みます。
*シート名をテーブル名、一行目をカラム名、二行目以降をデータ * として読み込みます。
* *パスはCLASSPATHで指定されているディレクトリをルートとする * 設定ファイルの絶対パスか、ファイル名のみを指定します。ファイル名 * のみの場合、テストケースと同じパッケージにあるものとします。
* * @param path Excelファイルのパス * @see org.seasar.extension.dataset.impl.XlsReader#read() * @see org.seasar.extension.dataset.impl.SqlWriter#write(DataSet) */ public void readXlsWriteDb(String path) { writeDb(readXls(path)); } /** *Excelファイルを読み込み、DBに書き込みます。
*シート名をテーブル名、一行目をカラム名、二行目以降をデータ * として読み込みます。
* *Excelの内容とDBのレコードとで主キーが一致するものがあれば、 * そのレコードを削除した後に書き込みます。
* *パスはCLASSPATHで指定されているディレクトリをルートとする * 設定ファイルの絶対パスか、ファイル名のみを指定します。ファイル名 * のみの場合、テストケースと同じパッケージにあるものとします。
* * @param path Excelファイルのパス * @see org.seasar.extension.dataset.impl.XlsReader#read() * @see org.seasar.extension.dataset.impl.SqlWriter#write(DataSet) */ public void readXlsReplaceDb(String path) { DataSet dataSet = readXls(path); deleteDb(dataSet); writeDb(dataSet); } /** *Excelファイルを読み込み、DBに書き込みます。
*シート名をテーブル名、一行目をカラム名、二行目以降をデータ * として読み込みます。
* *対象となるテーブルのレコードを全て削除した後に書き込みます。 *
* *パスはCLASSPATHで指定されているディレクトリをルートとする * 設定ファイルの絶対パスか、ファイル名のみを指定します。ファイル名 * のみの場合、テストケースと同じパッケージにあるものとします。
* * @see org.seasar.extension.dataset.impl.XlsReader#read() * @see org.seasar.extension.dataset.impl.SqlWriter#write(DataSet) */ public void readXlsAllReplaceDb(String path) { DataSet dataSet = readXls(path); for (int i = dataSet.getTableSize() - 1; i >= 0; --i) { deleteTable(dataSet.getTable(i).getTableName()); } writeDb(dataSet); } /** *DataSetに対応するDBのレコードを読み込み、DataSetを作成します * 。
* * @param dataSet 対象DBに対応するDataSet * @return 最新状態のDataSet * @see org.seasar.extension.dataset.impl.SqlReloadReader#read() */ public DataSet reload(DataSet dataSet) { return new SqlReloadReader(getDataSource(), dataSet).read(); } /** *DataTableに対応するDBのレコードを読み込み、DataTableを作成 * します。
* * @param table 対象DBに対応するDataTable * @return 最新状態のDataTable * @see org.seasar.extension.dataset.impl.SqlReloadTableReader#read() */ public DataTable reload(DataTable table) { return new SqlReloadTableReader(getDataSource(), table).read(); } /** *DataSetに対応するDBのレコードを削除します。
* * @param dataSet 対象DBに対応するDataSet * @see org.seasar.extension.dataset.impl.SqlDeleteTableWriter#write(DataTable) */ public void deleteDb(DataSet dataSet) { SqlDeleteTableWriter writer = new SqlDeleteTableWriter(getDataSource()); for (int i = dataSet.getTableSize() - 1; i >= 0; --i) { writer.write(dataSet.getTable(i)); } } /** *DBから指定するテーブルの全レコードを削除します。
* * @param tableName 削除対象のテーブル名 */ public void deleteTable(String tableName) { UpdateHandler handler = new BasicUpdateHandler(getDataSource(), "DELETE FROM " + tableName); handler.execute(null); } /** *DataSet同士を比較します。
*カラムの並び順は比較に影響しません。
* 数値は全てBigDecimalとして比較します。
DataSet同士を比較します。
*カラムの並び順は比較に影響しません。
* 数値は全てBigDecimalとして比較します。
DataTable同士を比較します。
*カラムの並び順は比較に影響しません。
* 数値は全てBigDecimalとして比較します。
カラムの並び順は比較に影響しません。
* 数値は全てBigDecimalとして比較します。
オブジェクトをDataSetと比較します。
*オブジェクトは、Bean、Map、BeanのList、MapのListのいずれか * でなければなりません。
* * Beanの場合はプロパティ名を、Mapの場合はキーをカラム名として * 比較します。オブジェクトをDataSetと比較します。
*オブジェクトは、Bean、Map、BeanのList、MapのListのいずれか * でなければなりません。
* *Beanの場合はプロパティ名を、Mapの場合はキーをカラム名として
* 比較します。
* カラムの並び順は比較に影響しません。
* 数値は全てBigDecimalとして比較します。
MapをDataSetと比較します。
*Mapのキーをカラム名として比較します。
* カラムの並び順は比較に影響しません。
* 数値は全てBigDecimalとして比較します。
MapのListをDataSetと比較します。
*Mapのキーをカラム名として比較します。
* カラムの並び順は比較に影響しません。
* 数値は全てBigDecimalとして比較します。
BeanをDataSetと比較します。
*Beanのプロパティ名をカラム名として比較します。
* カラムの並び順は比較に影響しません。
* 数値は全てBigDecimalとして比較します。
BeanのListをDataSetと比較します。
*Beanのプロパティ名をカラム名として比較します。
* カラムの並び順は比較に影響しません。
* 数値は全てBigDecimalとして比較します。
コンテナ初期化後に実行されるセットアップメソッドです。
*必要な場合にオーバーライドしてください。
* * @throws Throwable */ protected void setUpAfterContainerInit() throws Throwable { } /** *setup() 後に実行されるテストメソッドごとのセットアップ * メソッドです。
*testXxx() というメソッドの場合、setUpXxx() という名前で * セットアップメソッドを作成しておくと、自動的に実行されます。
* * @throws Throwable */ protected void setUpForEachTestMethod() throws Throwable { invoke("setUp" + getTargetName()); } /** *コンテナ終了処理前に実行される終了処理メソッドです。
*必要な場合にオーバーライドしてください。
* * @throws Throwable */ protected void tearDownBeforeContainerDestroy() throws Throwable { } /** *tearDown() 前に実行されるテストメソッドごとの終了処理メソッド * です。
*testXxx() というメソッドの場合、tearDownXxx() という名前で * 終了処理メソッドを作成しておくと、自動的に実行されます。
* * @throws Throwable */ protected void tearDownForEachTestMethod() throws Throwable { invoke("tearDown" + getTargetName()); } private String getTargetName() { return getName().substring(4); } private void invoke(String methodName) throws Throwable { try { Method method = ClassUtil.getMethod(getClass(), methodName, null); MethodUtil.invoke(method, this, null); } catch (NoSuchMethodRuntimeException ignore) { } } private void bindFields() throws Throwable { bindedFields_ = new ArrayList(); for (Class clazz = getClass(); clazz != S2TestCase.class && clazz != null; clazz = clazz.getSuperclass()) { Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; ++i) { bindField(fields[i]); } } } private void bindField(Field field) { if (isAutoBindable(field)) { field.setAccessible(true); if (FieldUtil.get(field, this) != null) { return; } String name = normalizeName(field.getName()); Object component = null; if (getContainer().hasComponentDef(name) && field.getType().isAssignableFrom( getComponentDef(name).getComponentClass())) { component = getComponent(name); } else if (getContainer().hasComponentDef(field.getType())) { component = getComponent(field.getType()); } if (component != null) { FieldUtil.set(field, this, component); bindedFields_.add(field); } } } private String normalizeName(String name) { return StringUtil.replace(name, "_", ""); } private boolean isAutoBindable(Field field) { int modifiers = field.getModifiers(); return !Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers) && !field.getType().isPrimitive(); } private void unbindFields() { for (int i = 0; i < bindedFields_.size(); ++i) { Field field = (Field) bindedFields_.get(i); try { field.set(this, null); } catch (IllegalArgumentException e) { System.err.println(e); } catch (IllegalAccessException e) { System.err.println(e); } } } private void runTestTx() throws Throwable { TransactionManager tm = null; if (needTransaction()) { try { tm = (TransactionManager) getComponent(TransactionManager.class); tm.begin(); } catch (Throwable t) { System.err.println(t); } } try { runTest(); } finally { if (tm != null) { tm.rollback(); } } } private boolean needTransaction() { return getName().endsWith("Tx"); } private void setupDataSource() { try { if (container_.hasComponentDef(DATASOURCE_NAME)) { dataSource_ = (DataSource) container_ .getComponent(DATASOURCE_NAME); } else if (container_.hasComponentDef(DataSource.class)) { dataSource_ = (DataSource) container_ .getComponent(DataSource.class); } } catch (Throwable t) { System.err.println(t); } } private void tearDownDataSource() { dbMetaData_ = null; if (connection_ != null) { ConnectionUtil.close(connection_); connection_ = null; } dataSource_ = null; } }