| Testing framework based on JUnit is integrated with S2 to make tests on development using containers fun. Followings are some features: S2Container is automatically generated for each test method(textXXX). register(),getComponent(), and include() for a S2Container is made available. If include() path is the same package as test class package, is is possible to omit path in a package. As an example, if aaa.bbb.CccTest class includes aaa/bbb/hoge.dicon, it is possible to write include statement as include("hoge.dicon"). TestCase fields that are not static nor final are automatically set with underscore character (_) omitted from their names. TestCase fields with interface type variable definitions are automatically set by S2Container. Values automatically assigned are cleared (set to null) afer test method ends. If test method(testXxx) is defined with corresponding setUpXxx() and tearDownXxx() methods, it is invoked after setUp() and before tearDown() are invoked. Thus, it makes initialization and finalization process easier. By ending test method names with Tx(e.g.testXxxTx) in include("j2ee.dicon") file, transaction is initiated just before the test method and rolled back after the test method is invoked. Thus, it is unnecessary to recovery database data after each test. ## Example Let's look atautomatic transaction processing which tests RequiredInterceptorTest. #### TxBean.java
    package test.org.seasar.extension.tx;

    import javax.transaction.SystemException;

    public interface TxBean {

        public boolean hasTransaction() throws SystemException;
    }
#### TxBeanImpl.java
    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;
        }
    }
#### Required InterceptioTest.dicon
    <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>
#### RequiredInterceptorTest.java
    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()});
        }
    }
The example demonstrates component construction and access are automatically handled by S2TestCase allowing each test method to focus on what really is to be tested. ## Testing with Database S2 has a feature to make testing with database easier. Let's look at an example. To execute a simple SQL statement, S2JDBC framework is used. ## Testing a Select statement This example inquires empno in EMP table using DAO. When empno=9900 is inquired, a result set consisting of a join of emp table where empno=9900 and dept table where deptno=99 should be returned. To test this case, Emp table, dept table, and data to verify the result are necessary. Verification data is entered in MS Excel with sheet name as a table name, first row containing column name, and following rows holding test verification data. It si possible to manually enter all the verification data, but we will generate result data from the database table. Please refer to setup to initialize HSQLDB database. We will use Db2Excel to create Excel data from the database table. ### test/examples/unit/Db2Excel.dicon
    <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>
SqlReader is used to read database data into DataSet. The first argument to addTable() is table name (sheet name). The second argument is the condition. XlsWriter writes out DataSet to Excel. File path is specified in the constructor. File path is relative to Eclipse's default file output folder. ### test.examples.unit.Db2Excel
    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();
            }
        }
    }
Database data can be written out to Excel simply by invoking read() method in SqlReader and write() method in XlsWriter. Refresh view on test.examples.unit package in Eclipse to confirm that getEmployeePrepare.xls was created. Right click on getEmployeePrepare.xls and choose Open -> System editor to open the file in Excel (MS Excel must be installed in the machine). If you just double click on getEmployeePrepase.xls, Excel is opened within Eclipse. In emp sheet, change EMPNO to 9900 and ENAME to SCOTT2. In dept sheet, chante DEPTNO to 99 and DNAME to RESEARCH2. Save the result and exit Excel. Next, right click EmployeePrepare.xls and choose Refresh to tell Eclipse that we just edited the file. Eclipse should copy the book to the default output folder. Next, we need to prepare verification data. Edit Db2Excel.dicon as follows: ### test/examples/unit/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>
Create getEmployeeResult.xls file in the same manner as above. Set EMPNO=9900 and ENAME=SCOTT2 in emp sheet and DEPTNO=99 and DNAME=RESEARCH2 in dept sheet. Let's start testing! ### test.examples.unit.EmployeeDaoImplTest
    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() reads app.dicon. By ending the test method name with Tx as testGetEmployeeTx in the above example, a transaction is started before the test method is called and it is rolled back afterward so the database will be left unmodified by the test. readXlsWriteDb() and readXlsAllReplaceDb() saves test data into the database. When Excel file is in the same package as the test class, package path may be omitted. Invoke readXlsWriteDb() and readXlsAllReplaceDb() in first row of textXxxTx() so database can rollback after the test. These methods insert data after deleting data from sheets in the opposite direction from their definitions. When using readXlsAllReplaceDb(), it may sometimes be necessary to create an empty sheet to avoid foreign key constraint error. As an example, if there is a foreign key constraint on table A to table B, it is necessary to create table B even though only data from table A is to be used. It is necessary to define table A then define table B. If interface variable(dao_) is declared as an interface, it will be taken out from S2Container and set before the test method is started. Data returned from Dao will be converted to DataTable so they can be compared with data from Excel. DataTable.setupColumns(Class) is used to construct column data from JavaBeans meta information. Result of calling Dao from DataTable.copyFrom() is copied to DataTable. copyFrom method may have JavaBeans, Map, JavaBeans, orMap List as an argument. readXls() reads data verification data from Excel and compares it with Dao result. |
Trans. by H.Ozawa | Copyright© 2004-2005, The Seasar Foundation and the others. All rights reserved.