ä¸ÞZ¾èµ–于其他外部¾pÈ»ŸåQˆå¦‚æ•°æ®åº“或其他接å£åQ‰çš„ä»£ç ¾~–写å•å…ƒ‹¹‹è¯•是一件很困难的工作。在˜q™ç§æƒ…况下,有效的å•元必™å»é𔼛ÀLµ‹è¯•对象和外部ä¾èµ–åQŒä»¥ä¾¿ç®¡ç†æµ‹è¯•对象的状æ€å’Œè¡ŒäØ“ã€?/p>
使用mock object对象åQŒæ˜¯éš”离外部ä¾èµ–的一个有效方法。如果我们的‹¹‹è¯•对象是ä¾èµ–于DAO的代ç ,mock object技术很方便。但如果‹¹‹è¯•å¯¹è±¡å˜æˆäº†DAO本èínåQŒåˆå¦‚何˜q›è¡Œå•å…ƒ‹¹‹è¯•呢?
å¼€æºçš„DbUnit™å¹ç›®åQŒäؓ以上的问题æä¾›äº†ä¸€ä¸ªç›¸å½“优雅的解决æ–ÒŽ¡ˆã€‚ä‹É用DbUnitåQŒå¼€å‘äh员å¯ä»¥æŽ§åˆ¶æµ‹è¯•æ•°æ®åº“的状æ€ã€‚进行一个DAOå•å…ƒ‹¹‹è¯•之å‰åQŒDbUnit为数æ®åº“准备好åˆå§‹åŒ–æ•°æ®åQ›è€Œåœ¨‹¹‹è¯•¾l“æŸæ—Óž¼ŒDbUnit会把数æ®åº“çŠ¶æ€æ¢å¤åˆ°‹¹‹è¯•å‰çš„状æ€ã€?/p>
下é¢çš„例åä‹É用DbUnit为iBATIS SqlMapçš„DAO¾~–写å•å…ƒ‹¹‹è¯•ã€?/p>
准备‹¹‹è¯•æ•°æ®
首先åQŒè¦ä¸ºå•元测试准备数æ®ã€‚ä‹É用DbUnitåQŒæˆ‘们å¯ä»¥ç”¨XMLæ–‡äšgæ¥å‡†å¤‡æµ‹è¯•æ•°æ®é›†ã€‚下é¢çš„XMLæ–‡äšg¿UîCØ“ç›®æ ‡æ•°æ®åº“çš„Seed FileåQŒä»£è¡¨ç›®æ ‡æ•°æ®åº“的表å和数æ®åQŒå®ƒä¸ºæµ‹è¯•准备了两个Employee的数æ®ã€‚employee对应数æ®åº“的表ååQŒemployee_uidã€start_dateã€first_nameå’Œlast_name都是表employee的列åã€?/span>
<?xml version="1.0" encoding="GB2312"?>
<dataset>
   <employee employee_uid="0001"
      start_date="2001-01-01"
      first_name="liutao"
      last_name="liutao" />
  Â
   <employee employee_uid="0002"
      start_date="2001-04-01"
      first_name="wangchuang"
      last_name="wangchuang" />
</dataset>
¾~ºçœæƒ…况下,DbUnit在å•元测试开始之å‰åˆ 除Seed File䏿‰€æœ‰è¡¨çš„æ•°æ®ï¼Œç„¶åŽå¯¼å…¥Seed File的测试数æ®ã€‚在Seed Fileä¸ä¸å˜åœ¨çš„表åQŒDbUnit则ä¸å¤„ç†ã€?br />Seed Fileå¯ä»¥æ‰‹å·¥¾~–写åQŒä¹Ÿå¯ä»¥ç”¨ç¨‹åºå¯¼å‡ºçŽ°æœ‰çš„æ•°æ®åº“æ•°æ®åƈ生æˆã€?br />
SqlMap代ç
æˆ‘ä»¬è¦æµ‹è¯•çš„SqlMapæ˜ å°„æ–‡äšg如下所½Cºï¼š
<select id="queryEmployeeById" parameterClass="java.lang.String"
   resultClass="domain.Employee">
   select employee_uid as userId,
      start_date as startDate,
      first_name as firstName,
      last_name as lastName
   from EMPLOYEE where employee_uid=#value#
</select>
<delete id="removeEmployeeById" parameterClass="java.lang.String">
   delete from EMPLOYEE where employee_uid=#value#
</delete>
<update id="updateEmpoyee" parameterClass="domain.Employee">
   update EMPLOYEE
   set start_date=#startDate#,
   first_name=#firstName#,
   last_name=#lastName#
   where employee_uid=#userId#
</update>
<insert id="insertEmployee" parameterClass="domain.Employee">
   insert into employee (employee_uid,
      start_date, first_name, last_name)
      values (#userId#, #startDate#, #firstName#, #lastName#)
</insert>
¾~–写DbUnit TestCase
ä¸ÞZº†æ–¹ä¾¿‹¹‹è¯•åQŒé¦–å…ˆäØ“SqlMapçš„å•元测试编写一个抽象的‹¹‹è¯•基类åQŒä»£ç 如下ã€?/span>
public abstract class BaseSqlMapTest extends DatabaseTestCase {
   protected static SqlMapClient sqlMap;
   protected IDatabaseConnection getConnection() throws Exception {
      return new DatabaseConnection(getJdbcConnection());
   }
   protected void setUp() throws Exception {
      super.setUp();
      init();
   }
   protected void tearDown() throws Exception {
      super.tearDown();
      getConnection().close();
      if (sqlMap != null) {
         DataSource ds = sqlMap.getDataSource();
         Connection conn = ds.getConnection();
         conn.close();
      }
   }
   protected void init() throws Exception {
      initSqlMap("sqlmap/SqlMapConfig.xml", null);
   }
   protected SqlMapClient getSqlMapClient() {
      return sqlMap;
   }
   protected void initSqlMap(String configFile, Properties props)
         throws Exception {
      Reader reader = Resources.getResourceAsReader(configFile);
      sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader, props);
      reader.close();
   }
   protected void initScript(String script) throws Exception {
      DataSource ds = sqlMap.getDataSource();
      Connection conn = ds.getConnection();
     Â
      Reader reader = Resources.getResourceAsReader(script);
      ScriptRunner runner = new ScriptRunner();
      runner.setStopOnError(false);
      runner.setLogWriter(null);
      runner.setErrorLogWriter(null);
      runner.runScript(conn, reader);
      conn.commit();
      conn.close();
      reader.close();
   }
   private Connection getJdbcConnection() throws Exception {
      Properties props = new Properties();
      props.load(Resources.getResourceAsStream("sqlmap/SqlMapConfig.properties"));
      Class driver = Class.forName(props.getProperty("driver"));
      Connection conn = DriverManager.getConnection(props.getProperty("url"),
            props.getProperty("username"), props.getProperty("password"));
      return conn;
   }
}
ç„¶åŽä¸ºæ¯ä¸ªSqlMapæ˜ å°„æ–‡äšg¾~–写一个测试用例,extends上é¢çš„æŠ½è±¡ç±»ã€‚如¾~–写Employ.xml的测试用例如下,它覆盖了DbUnitçš„DatabaseTestCase¾cÈš„getDataSetæ–ÒŽ³•ã€?br />
public class EmployeeDaoTest extends BaseSqlMapTest {
  Â
   protected IDataSet getDataSet() throws Exception {
      Reader reader = Resources.getResourceAsReader("config/employee_seed.xml");
      return new FlatXmlDataSet(reader);
   }
   public void testQueryEmpoyeeById() throws Exception {
      String id = "0001";
      Employee emp = (Employee)sqlMap.queryForObject("queryEmployeeById", id);
      assertNotNull(emp);
      assertEquals("0001", emp.getUserId());
      assertEquals("liutao", emp.getFirstName());
   }
   public void testRemoveEmployeeById() throws Exception {
      String id = "0001";
      int num = sqlMap.delete("removeEmployeeById", id);
      assertEquals(1, num);
     Â
      // 注愘q™é‡Œ, ¼‹®è®¤åˆ 除ä¸èƒ½ä½¿ç”¨SqlMap的查è¯? 很奇怪ï¼
      ITable table = getConnection().createQueryTable("removed",
            "select * from employee where employee_uid='0001'");
      assertEquals(0, table.getRowCount());
   }
   public void testUpdateEmployee() throws Exception {
      String id = "0002";
      Employee emp = (Employee)sqlMap.queryForObject("queryEmployeeById", id);
      emp.setLastName("wch");
      sqlMap.update("updateEmpoyee", emp);
     Â
      Employee emp1 = (Employee)sqlMap.queryForObject("queryEmployeeById", id);
      assertEquals("wch", emp1.getLastName());
   }
   public void testInsertEmployee() throws Exception {
      Employee emp = new Employee();
      emp.setUserId("0005");
      emp.setStartDate("2003-09-09");
      emp.setFirstName("macy");
      emp.setLastName("macy");
      sqlMap.insert("insertEmployee", emp);
     Â
      Employee emp1 = (Employee)sqlMap.queryForObject("queryEmployeeById", "0005");
      assertEquals(emp.getFirstName(), emp1.getFirstName());
      assertEquals(emp.getStartDate(), emp1.getStartDate());
   }
}
以上例åä¸çš„¾l¿è‰²ä»£ç 部分使用ITableæŽ¥å£æ¥æŸ¥è¯¢å·²åˆ 除的数æ®ã€‚å› ä¸ÞZ‹É用SqlMapClient.queryForObjectæ–ÒŽ³•查询åQŒå·²åˆ 除的数æ®è¿˜å˜åœ¨åQŒçœŸå¥‡æ€ªï¼ˆæœ‰æ—¶é—´å†ç ”ç©¶åQ‰ã€?br />
DbUnitçš„æ–a€
我们å¯ä»¥ä½¿ç”¨DbUnitçš„Assertion¾cÈš„æ–ÒŽ³•æ¥æ¯”è¾ƒæ•°æ®æ˜¯å¦ç›¸åŒã€?br />
public class Assertion {
   public static void assertEquals(ITable expected, ITable actual)
   public static void assertEquals(IDataSet expected, IDataSet actual)
}
DatabaseTestCaseçš„getSetUpOperationå’ŒgetTearDownOperationæ–ÒŽ³•
¾~ºçœæƒ…况下,DbUnit执行æ¯ä¸ª‹¹‹è¯•å‰ï¼Œéƒ½ä¼šæ‰§è¡ŒCLEAN_INSERTæ“作åQŒåˆ 除Seed File䏿‰€æœ‰è¡¨çš„æ•°æ®ï¼Œòq¶æ’入文件的‹¹‹è¯•æ•°æ®ã€‚ä½ å¯ä»¥é€šè¿‡è¦†ç›–getSetUpOperationå’ŒgetTearDownOperationæ–ÒŽ³•改å˜setUpå’ŒtearDown的行为ã€?br />
protected DatabaseOperation getSetUpOperation() throws Exception {
   return DatabaseOperation.REFRESH;
}
protected DatabaseOperation
getTearDownOperation()
throws Exception {
  Â
return DatabaseOperation.NONE;
}
REFRESHæ“作执行‹¹‹è¯•å‰åÆˆä¸æ‰§è¡ŒCLEANæ“作åQŒåªæ˜¯å¯¼å…¥æ–‡ä»¶ä¸çš„æ•°æ®ï¼Œå¦‚æžœç›®æ ‡æ•°æ®åº“æ•°æ®å·²å˜åœ¨åQŒDbUnit使用文äšgçš„æ•°æ®æ¥æ›´æ–°æ•°æ®åº“ã€?/span>
使用Ant
上é¢çš„æ–¹æ³•通过extends DbUnitçš„DatabaseTestCaseæ¥æŽ§åˆ¶æ•°æ®åº“的状æ€ã€‚è€?/span>
使用DbUnitçš„Ant TaskåQŒå®Œå…¨å¯ä»¥é€šè¿‡Antè„šæœ¬çš„æ–¹å¼æ¥å®žçްã€?/span>
<taskdef name="dbunit" classname="org.dbunit.ant.DbUnitTask"/>
<!-- 执行set up æ“作 -->
<dbunit driver="org.hsqldb.jdbcDriver"
       url="jdbc:hsqldb:hsql://localhost/xdb"
       userid="sa" password="">
   <operation type="INSERT" src="employee_seed.xml"/>
</dbunit>
<!-- run all tests in the source tree -->
<junit printsummary="yes" haltonfailure="yes">
 <formatter type="xml"/>
 <batchtest fork="yes" todir="${reports.tests}">
   <fileset dir="${src.tests}">
     <include name="**/*Test*.java"/>
   </fileset>
 </batchtest>
</junit>
<!-- 执行tear down æ“作 -->
<dbunit driver="org.hsqldb.jdbcDriver"
       url="jdbc:hsqldb:hsql://localhost/xdb"
       userid="sa" password="">
   <operation type="DELETE" src="employee_seed.xml"/>
</dbunit>
以上的Ant脚本把junit task攑֜¨DbUnitçš„Taskä¸é—´åQŒå¯ä»¥è¾¾åˆ°æŽ§åˆ¶æ•°æ®åº“状æ€çš„ç›®æ ‡ã€?br />
ç”±æ¤å¯çŸ¥åQŒDbUnitå¯ä»¥ç‰|´»æŽ§åˆ¶ç›®æ ‡æ•°æ®åº“çš„‹¹‹è¯•状æ€ï¼Œä»Žè€Œä‹É¾~–写SqlMapå•å…ƒ‹¹‹è¯•å˜å¾—æ›´åŠ è½ÀL¾ã€?br />
本文抄è¢äº†èµ„æºåˆ—表的“Effective Unit Test with DbUnitâ€ï¼Œä½†é‡æ–°ç¼–å†™äº†ä»£ç ½CÞZ¾‹ã€?br />
¾|‘上资æº
1�a >DbUnit Framework
2�a >Effective Unit Testing with DbUnit
3�a >Control your test-environement with DbUnit and Anthill