<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    qileilove

    blog已經(jīng)轉(zhuǎn)移至github,大家請(qǐng)?jiān)L問 http://qaseven.github.io/

    使用DBUnit做單元測試

     DBUnit是一個(gè)方便的數(shù)據(jù)準(zhǔn)備工具, 方便于我們做單元測試的時(shí)候準(zhǔn)備數(shù)據(jù), 它的數(shù)據(jù)準(zhǔn)備是基于XML格式的, 如下:
    <?xmlversion='1.0'encoding='UTF-8'?>
    <dataset>
    <YourTableName_1Field_1="1"Field_2="f2"Field_3="f3"/>
    <YourTableName_1Field_1="2"Field_2="f2_1"Field_3="f3_1"/>
    <YourTableName_2Field_1="1"Field_2="2"/>
    </dataset>
      DBUnit的一個(gè)XML數(shù)據(jù)文件中,可以同時(shí)放多個(gè)表的數(shù)據(jù),并且可以方便的把上面XML中準(zhǔn)備的數(shù)據(jù)插入倒數(shù)據(jù)庫中. 只需要使用下面簡單的代碼就可以做到:
    protected ReplacementDataSet createDataSet(InputStream is) throws Exception {
    return new ReplacementDataSet(new FlatXmlDataSetBuilder().build(is));
    }
    ReplacementDataSet createDataSet = createDataSet(Thread.currentThread().getContextClassLoader().getResourceAsStream("data.xml"));
    DatabaseOperation.INSERT.execute(iconn, createDataSet);
      注:準(zhǔn)備這處XML數(shù)據(jù)文件時(shí),一定要把同一個(gè)表中字段數(shù)最多的記錄放在前面,因?yàn)镈BUnit在根據(jù)數(shù)據(jù)XML文件準(zhǔn)備表的元數(shù)據(jù)字段的時(shí)候,是以當(dāng)前表的第一記錄為主的。如下面這個(gè)XML文件:
    <?xml version='1.0' encoding='UTF-8'?>
    <dataset>
    <YourTableName_1 Field_1="1" Field_2="f2"/>
    <YourTableName_1 Field_1="2" Field_2="f2_1" Field_3="f3_1"/>
    <YourTableName_2 Field_1="1" Field_2="2"/>
    </dataset>
      Table YourTableName_1有三個(gè)字段,但是第三個(gè)字段Field_3是允許為空的,所以上面的XML是可以這樣寫的,并且DBUnit在執(zhí)行插入的時(shí)候也不會(huì)報(bào)錯(cuò),但是這里會(huì)出現(xiàn)一個(gè)問題,因?yàn)镈BUnit在第一次分析到table YourTableName_1時(shí),第一條記錄只有兩個(gè)字段,因而它在記錄table YourTableName_1的字段的時(shí)候,就只會(huì)記錄兩個(gè)到這個(gè)表的元數(shù)據(jù)信息,因而在對(duì)在對(duì)后面記錄進(jìn)行數(shù)據(jù)處理的時(shí)候,只會(huì)取后面記錄的兩個(gè)字段,而第三個(gè)字段是不會(huì)被插入到數(shù)據(jù)庫中。解決這個(gè)問題很簡單,把YourTableName_1的第二條記錄和第一條記錄給換一下位置就好了。 同理,在數(shù)據(jù)result數(shù)據(jù)文件的時(shí)候,也要遵循這樣的規(guī)則,否則會(huì)得不到想要的結(jié)果的。這是經(jīng)驗(yàn)總結(jié)。
     你可能會(huì)擔(dān)心如果是要準(zhǔn)備的數(shù)據(jù)比較多是不是會(huì)比較麻煩,如上百條的數(shù)據(jù)準(zhǔn)備,這個(gè)可以不用擔(dān)心,因?yàn)槭褂肈BUnit可以方便的從數(shù)據(jù)庫中導(dǎo)出數(shù)據(jù)到指定的文件中,然后供我們使用,使用以下這個(gè)方法就可以導(dǎo)出數(shù)據(jù):
    /**
    * Export data for the table names by the given IDatabaseConnection into the resultFile.<br>
    * The export data will be DBUnit format.
    *
    * @param conn
    * @param tableNameList
    * @param resultFile
    * @throws SQLException
    * @throws DatabaseUnitException
    * @throws FileNotFoundException
    * @throws IOException
    */
    public void exportData(IDatabaseConnection iconn, List<String> tableNameList, String resultFile) throws SQLException, DatabaseUnitException, FileNotFoundException, IOException {
    QueryDataSet dataSet = null;
    if (iconn == null) {
    return;
    }
    if (tableNameList == null || tableNameList.size() == 0) {
    return;
    }
    try {
    dataSet = new QueryDataSet(iconn);
    for (String tableName : tableNameList) {
    dataSet.addTable(tableName);
    }
    } finally {
    if (dataSet != null) {
    FlatXmlDataSet.write(dataSet, new FileOutputStream(resultFile));
    }
    }
    }
      DBUnit的另一個(gè)非常有用的功能,就是對(duì)執(zhí)行結(jié)果進(jìn)行比較,這樣可以直接得到執(zhí)行結(jié)果是否正確。 操作方式是準(zhǔn)備一個(gè)執(zhí)行期待結(jié)果的XML文件,再準(zhǔn)備一條從數(shù)據(jù)庫查詢結(jié)果的SQL。這里有一個(gè)經(jīng)驗(yàn)非常重要,那就是用于查詢的執(zhí)行結(jié)果的SQL文件,最好是加上某個(gè)關(guān)鍵字段的ORDER BY語句,否則可能會(huì)因?yàn)橛涗浀捻樞蚨容^失敗,因?yàn)镈BUnit是把查詢出來的結(jié)果和準(zhǔn)備的結(jié)果進(jìn)行一一對(duì)應(yīng)的比較。當(dāng)然,既然SQL查詢都加上了排序,那我們的結(jié)果XML文件,也應(yīng)該是根據(jù)關(guān)鍵字段排好序的結(jié)果的,否則也會(huì)因?yàn)橛涗浀捻樞騿栴}而比較失敗。
     你可能會(huì)擔(dān)心如果是要準(zhǔn)備的數(shù)據(jù)比較多是不是會(huì)比較麻煩,如上百條的數(shù)據(jù)準(zhǔn)備,這個(gè)可以不用擔(dān)心,因?yàn)槭褂肈BUnit可以方便的從數(shù)據(jù)庫中導(dǎo)出數(shù)據(jù)到指定的文件中,然后供我們使用,使用以下這個(gè)方法就可以導(dǎo)出數(shù)據(jù):
    /**
    * Export data for the table names by the given IDatabaseConnection into the resultFile.<br>
    * The export data will be DBUnit format.
    *
    * @param conn
    * @param tableNameList
    * @param resultFile
    * @throws SQLException
    * @throws DatabaseUnitException
    * @throws FileNotFoundException
    * @throws IOException
    */
    public void exportData(IDatabaseConnection iconn, List<String> tableNameList, String resultFile) throws SQLException, DatabaseUnitException, FileNotFoundException, IOException {
    QueryDataSet dataSet = null;
    if (iconn == null) {
    return;
    }
    if (tableNameList == null || tableNameList.size() == 0) {
    return;
    }
    try {
    dataSet = new QueryDataSet(iconn);
    for (String tableName : tableNameList) {
    dataSet.addTable(tableName);
    }
    } finally {
    if (dataSet != null) {
    FlatXmlDataSet.write(dataSet, new FileOutputStream(resultFile));
    }
    }
    }
      DBUnit的另一個(gè)非常有用的功能,就是對(duì)執(zhí)行結(jié)果進(jìn)行比較,這樣可以直接得到執(zhí)行結(jié)果是否正確。 操作方式是準(zhǔn)備一個(gè)執(zhí)行期待結(jié)果的XML文件,再準(zhǔn)備一條從數(shù)據(jù)庫查詢結(jié)果的SQL。這里有一個(gè)經(jīng)驗(yàn)非常重要,那就是用于查詢的執(zhí)行結(jié)果的SQL文件,最好是加上某個(gè)關(guān)鍵字段的ORDER BY語句,否則可能會(huì)因?yàn)橛涗浀捻樞蚨容^失敗,因?yàn)镈BUnit是把查詢出來的結(jié)果和準(zhǔn)備的結(jié)果進(jìn)行一一對(duì)應(yīng)的比較。當(dāng)然,既然SQL查詢都加上了排序,那我們的結(jié)果XML文件,也應(yīng)該是根據(jù)關(guān)鍵字段排好序的結(jié)果的,否則也會(huì)因?yàn)橛涗浀捻樞騿栴}而比較失敗。

      上面的是熱身,該來點(diǎn)實(shí)際的東西了, 弄個(gè)真實(shí)的實(shí)例來看看,下面是一個(gè)用于DBUnit測試的抽象類:
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.Properties;
    import java.util.TreeMap;
    import junit.framework.Assert;
    import org.dbunit.Assertion;
    import org.dbunit.DatabaseUnitException;
    import org.dbunit.IDatabaseTester;
    import org.dbunit.JdbcDatabaseTester;
    import org.dbunit.database.DatabaseConnection;
    import org.dbunit.database.IDatabaseConnection;
    import org.dbunit.database.QueryDataSet;
    import org.dbunit.dataset.Column;
    import org.dbunit.dataset.IDataSet;
    import org.dbunit.dataset.ITable;
    import org.dbunit.dataset.ReplacementDataSet;
    import org.dbunit.dataset.filter.DefaultColumnFilter;
    import org.dbunit.dataset.xml.FlatXmlDataSet;
    import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
    import org.junit.runner.RunWith;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.TestExecutionListeners;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
    import org.springframework.transaction.annotation.Transactional;
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = { "classpath:/spring.xml" })
    @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class })
    @Transactional
    public abstract class BasedTestCase {
    protected static Properties properties = new Properties();
    static {
    try {
    /**
    * The DatabaseConfig.properties stores the database configuration information. It's like this: <br>
    * driverClass=oracle.jdbc.OracleDriver<br>
    * db_inst=jdbc:oracle:thin:@1.1.1.1:1521:schema<br>
    * db_user=username<br>
    * db_pwd=password<br>
    */
    properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("DatabaseConfig.properties"));
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    /**
    * This abstract is used for prepare data before do the real method call.
    *
    * @param iconn
    * @throws Exception
    */
    protected abstract void prepareData(IDatabaseConnection iconn) throws Exception;
    /**
    * Execute one sql
    *
    * @param iconn
    * @param sql
    * @throws Exception
    */
    protected void execSql(IDatabaseConnection iconn, String sql) throws Exception {
    Connection con = iconn.getConnection();
    Statement stmt = con.createStatement();
    try {
    stmt.execute(sql);
    } finally {
    if (stmt != null) {
    stmt.close();
    }
    }
    }
    /**
    * Get IDatabaseConnection connection
    *
    * @return
    * @throws Exception
    */
    protected IDatabaseConnection getIDatabaseConnection() throws Exception {
    String db_inst = "", db_user = "", db_pwd = "", driverClass = "";
    //The default is commit the record
    db_user = properties.getProperty("db_user");
    db_inst = properties.getProperty("db_inst");
    db_pwd = properties.getProperty("db_pwd");
    driverClass = properties.getProperty("driverClass");
    IDatabaseConnection iconn = null;
    IDatabaseTester databaseTester;
    databaseTester = new JdbcDatabaseTester(driverClass, db_inst, db_user, db_pwd);
    iconn = databaseTester.getConnection();
    return iconn;
    }
    /**
    * This is used to assert the data from table and the expected data set. If all of the them has the same records, then the assert is true.
    *
    * @param tableName
    * @param sql
    * @param expectedDataSet
    * @param iconn
    * @throws Exception
    */
    protected void assertDataSet(String tableName, String sql, IDataSet expectedDataSet, IDatabaseConnection iconn) throws Exception {
    printDataAsXml(iconn, tableName, sql);
    QueryDataSet loadedDataSet = new QueryDataSet(iconn);
    loadedDataSet.addTable(tableName, sql);
    ITable table1 = loadedDataSet.getTable(tableName);
    ITable table2 = expectedDataSet.getTable(tableName);
    Assert.assertEquals(table2.getRowCount(), table1.getRowCount());
    DefaultColumnFilter.includedColumnsTable(table1, table2.getTableMetaData().getColumns());
    Assertion.assertEquals(table2, table1);
    }
    /**
    * Create the data set by input stream which read from the dbunit xml data file.
    *
    * @param is
    * @return
    * @throws Exception
    */
    protected ReplacementDataSet createDataSet(InputStream is) throws Exception {
    return new ReplacementDataSet(new FlatXmlDataSetBuilder().build(is));
    }
    /**
    * Convert the data in the ITable to List
    *
    * @param table
    * @return
    * @throws Exception
    */
    private List<Map<?, ?>> getDataFromTable(ITable table) throws Exception {
    List<Map<?, ?>> ret = new ArrayList<Map<?, ?>>();
    int count_table = table.getRowCount();
    if (count_table > 0) {
    Column[] columns = table.getTableMetaData().getColumns();
    for (int i = 0; i < count_table; i++) {
    Map<String, Object> map = new TreeMap<String, Object>();
    for (Column column : columns) {
    map.put(column.getColumnName().toUpperCase(), table.getValue(i, column.getColumnName()));
    }
    ret.add(map);
    }
    }
    return ret;
    }
    /**
    * Get data by the SQL and table name, then convert the data in the ITable to List
    *
    * @param iconn
    * @param tableName
    * @param sql
    * @return
    * @throws Exception
    */
    protected List<Map<?, ?>> getTableDataFromSql(IDatabaseConnection iconn, String tableName, String sql) throws Exception {
    ITable table = iconn.createQueryTable(tableName, sql);
    return getDataFromTable(table);
    }
    /**
    * Get data by the SQL and table name, then convert the data in the ITable to List. And the print the data as xml data format.
    *
    * @param iconn
    * @param tableName
    * @param sql
    * @throws Exception
    */
    protected void printDataAsXml(IDatabaseConnection iconn, String tableName, String sql) throws Exception {
    List<Map<?, ?>> datas = getTableDataFromSql(iconn, tableName, sql);
    StringBuffer sb;
    for (Map<?, ?> data : datas) {
    sb = new StringBuffer();
    sb.append("<" + tableName.toUpperCase() + " ");
    for (Object o : data.keySet()) {
    sb.append(o + "=\"" + data.get(o) + "\" ");
    }
    sb.append("/>");
    System.out.println(sb.toString());
    }
    }
    /**
    * Export data for the table names by the given Connection into the resultFile.<br>
    * The export data will be DBUnit format.
    *
    * @param conn
    * @param tableNameList
    * @param resultFile
    * @throws SQLException
    * @throws DatabaseUnitException
    * @throws FileNotFoundException
    * @throws IOException
    */
    public void exportData(Connection conn, List<String> tableNameList, String resultFile) throws SQLException, DatabaseUnitException, FileNotFoundException, IOException {
    if (conn == null) {
    return;
    }
    IDatabaseConnection iconn = new DatabaseConnection(conn);
    exportData(iconn, tableNameList, resultFile);
    }
    /**
    * Export data for the table names by the given IDatabaseConnection into the resultFile.<br>
    * The export data will be DBUnit format.
    *
    * @param conn
    * @param tableNameList
    * @param resultFile
    * @throws SQLException
    * @throws DatabaseUnitException
    * @throws FileNotFoundException
    * @throws IOException
    */
    public void exportData(IDatabaseConnection iconn, List<String> tableNameList, String resultFile) throws SQLException, DatabaseUnitException, FileNotFoundException, IOException {
    QueryDataSet dataSet = null;
    if (iconn == null) {
    return;
    }
    if (tableNameList == null || tableNameList.size() == 0) {
    return;
    }
    try {
    dataSet = new QueryDataSet(iconn);
    for (String tableName : tableNameList) {
    dataSet.addTable(tableName);
    }
    } finally {
    if (dataSet != null) {
    FlatXmlDataSet.write(dataSet, new FileOutputStream(resultFile));
    }
    }
    }
    }


    這個(gè)抽象類里面有實(shí)用插入數(shù)據(jù)、導(dǎo)出數(shù)據(jù)及驗(yàn)證數(shù)據(jù)的實(shí)現(xiàn),也包括了數(shù)據(jù)庫連接的準(zhǔn)備,該類里面包含了一個(gè)抽象方法prepareData,因?yàn)槿魏问褂肈BUnit做單元測試的,應(yīng)該是少不了數(shù)據(jù)準(zhǔn)備這么一個(gè)過程,否則就只能夠使用數(shù)據(jù)庫中的現(xiàn)成數(shù)據(jù),這樣的單元測試是不靠譜的,因?yàn)閿?shù)據(jù)庫中的數(shù)據(jù)隨時(shí)可能發(fā)生變化,這里的抽象方法prepareData就相當(dāng)于在提醒寫單元測試的人,不要忘了準(zhǔn)備單元測試要用的數(shù)據(jù)。
      根據(jù)上面的思路,準(zhǔn)備一個(gè)用于測試的Table:
    create table YouTableName_1(
    filed_1 int,
    filed_2 varchar2(50),
    filed_3 varchar2(50)
    )
      用于測試的數(shù)據(jù):
    <?xml version='1.0' encoding='UTF-8'?>
    <dataset>
    <YouTableName_1 Filed_1="1" Filed_2="f2" Filed_3="f3"/>
    <YouTableName_1 Filed_1="2" Filed_2="f2_1" Filed_3="f3_1"/>
    </dataset>
      用于驗(yàn)證測試結(jié)果的數(shù)據(jù):
    <?xml version='1.0' encoding='UTF-8'?>
    <dataset>
    <YouTableName_1 Filed_1="1" Filed_2="a" Filed_3="a1"/>
    <YouTableName_1 Filed_1="2" Filed_2="b" Filed_3="b1"/>
    </dataset>
      我們這個(gè)測試非常簡單,就是把Filed_1為1的字段中Filed_2和Filed_3的字段的值分別設(shè)為"a"和"a1",把Filed_1為2的字段中Filed_2和Filed_3的字段的值分別設(shè)為"b"和"b1",做完測試后,數(shù)據(jù)庫中是不會(huì)插入我們的單元測試的數(shù)據(jù)的。
      下面這個(gè)類UpdateTest用于更新數(shù)據(jù):
    import java.sql.Connection;
    import java.sql.SQLException;
    public class UpdateTest {
    private static boolean commit = true;
    public UpdateTest() {
    }
    private void commit(Connection conn) throws SQLException {
    if (commit) {
    conn.commit();
    }
    }
    public void updateFiled(Connection conn) throws SQLException {
    conn.createStatement().execute("update YouTableName_1 set filed_2='a',filed_3='a1' where filed_1=1");
    conn.createStatement().execute("update YouTableName_1 set filed_2='b',filed_3='b1' where filed_1=2");
    commit(conn);
    }
    }
     下面這個(gè)MyTest類,就是用于單元測試的類:
    package com.ubs.cre.tools.datatool.ipl;
    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.sql.Connection;
    import java.sql.SQLException;
    import junit.framework.Assert;
    import org.dbunit.database.IDatabaseConnection;
    import org.dbunit.dataset.ReplacementDataSet;
    import org.dbunit.operation.DatabaseOperation;
    import org.junit.Test;
    import com.ubs.cre.BasedTestCase;
    public class MyTest extends BasedTestCase {
    @Test
    public void testSend() throws IOException, SQLException {
    Connection conn = null;
    Boolean result = Boolean.FALSE;
    IDatabaseConnection iconn = null;
    try {
    //Get DBUnit conneciton
    iconn = getIDatabaseConnection();
    //Get database connection
    conn = iconn.getConnection();
    //Set auto commit false
    conn.setAutoCommit(false);
    //prepare data
    prepareData(iconn);
    //use reflect to set the commit field to false
    Class<UpdateTest> clazz = UpdateTest.class;
    Field commitField = clazz.getDeclaredField("commit");
    commitField.setAccessible(true);
    commitField.setBoolean(clazz, false);
    //call the method updateFiled
    Method method = clazz.getDeclaredMethod("updateFiled", java.sql.Connection.class);
    method.setAccessible(true);
    //Before call the method, the clazz must be get an new install, because the called method "updateFiled" is not static.<br>
    //If the called method is static, it will not need newInstance.
    method.invoke(clazz.newInstance(), conn);
    // get result data set by result xml file
    ReplacementDataSet dataload_result = createDataSet(Thread.currentThread().getContextClassLoader().getResourceAsStream("MyTest_Result.xml"));
    // compare the data which get from database and the expected result file
    assertDataSet("YouTableName_1", "select filed_1,filed_2,filed_3 from YouTableName_1 order by filed_1", dataload_result, iconn);
    } catch (Exception e) {
    e.printStackTrace();
    Assert.assertTrue(result);
    } finally {
    if (conn != null) {
    conn.rollback();
    conn.close();
    }
    }
    }
    protected void prepareData(IDatabaseConnection iconn) throws Exception {
    //Remove the data from table YouTableName_1
    execSql(iconn, "delete from YouTableName_1");
    //INSERT TEST DATA
    ReplacementDataSet createDataSet = createDataSet(Thread.currentThread().getContextClassLoader().getResourceAsStream("MyTest.xml"));
    DatabaseOperation.INSERT.execute(iconn, createDataSet);
    }
    }
      好了,示例完了,非常的簡單,也非常的清晰,不過美中不足就是和DBUnit的代碼耦合度太高了,這過對(duì)于我們使用習(xí)慣了Spring的人來說,看起來是非常別扭的,后面我會(huì)寫另外一個(gè)與Spring集成的、完全非侵入式的測試實(shí)現(xiàn),等著吧。

    posted on 2013-11-25 10:35 順其自然EVO 閱讀(690) 評(píng)論(0)  編輯  收藏 所屬分類: 數(shù)據(jù)庫

    <2013年11月>
    272829303112
    3456789
    10111213141516
    17181920212223
    24252627282930
    1234567

    導(dǎo)航

    統(tǒng)計(jì)

    常用鏈接

    留言簿(55)

    隨筆分類

    隨筆檔案

    文章分類

    文章檔案

    搜索

    最新評(píng)論

    閱讀排行榜

    評(píng)論排行榜

    主站蜘蛛池模板: 免费看香港一级毛片| 成人A毛片免费观看网站| 日韩精品内射视频免费观看| 成人性生交视频免费观看| 亚洲国产综合专区在线电影| 亚洲AV无一区二区三区久久| 中国一级毛片免费看视频| 国产亚洲美女精品久久久| 九九视频高清视频免费观看| 亚洲国产精品视频| 国产高清对白在线观看免费91| 久久免费动漫品精老司机 | 久久精品亚洲AV久久久无码| 精品国产sm捆绑最大网免费站| heyzo亚洲精品日韩| 亚洲视频在线免费| 久久久无码精品亚洲日韩蜜桃| 亚洲丁香婷婷综合久久| 国产片免费在线观看| 色老头综合免费视频| 免费国产成人高清在线观看网站| 91亚洲国产成人久久精品网址| 91香蕉成人免费网站| 亚洲AV色香蕉一区二区| ww在线观视频免费观看| 亚洲第一街区偷拍街拍| 亚洲区小说区图片区| 青青草原1769久久免费播放| 亚洲码在线中文在线观看| 成人性生免费视频| 国产一级一毛免费黄片| 亚洲系列中文字幕| 国产乱子伦片免费观看中字| 亚洲伦理一二三四| 日产国产精品亚洲系列| 暖暖免费日本在线中文| 中日韩亚洲人成无码网站| 免费视频爱爱太爽了| 国产亚洲美女精品久久| 国产亚洲综合成人91精品| 亚洲欧洲免费无码|