用DbUnit進行數據庫集成測試


DbUnit是測試數據庫的利器,不過要想弄明白還是需要一番研究。好在它的源代碼不多,文檔也還算全。我就在此做一個總結吧。

DbUnit.NET是DbUnit的.NET版,不過只推出了alpha版,而且自從06年以后就不再更新了。Stack Overflow上有一個帖子,提出了一些替代方案。

現在的DbUnit要求在測試時繼承DBTestCase,而不是之前的DatabaseTestCase(前者繼承自后者,而后者繼承了junit的TestCase)。DatabaseTestCase包含兩個抽象方法,getConnection()getDataSet(),前者用來獲取數據庫連接,后者獲取要測試的數據集。

數據集

DbUnit可以把所有表的記錄存在一個數據集中:既可以是數據庫中的表,也可以是文件中的數據。我們在此用FlatXmlDataSet來演示。

順便提一句,DbUnit中還存在另一種格式的數據集XmlDataSet,它們的區別如下:

FaltXmlDataSet對應的XML文件里,元素名稱對應數據庫表名,元素的屬性(attribute)對應表的列。如:

<dataset>
    <Person Name="Kirin" Age="31" Location="Beijing"/>
    <Person Name="Jade" Age="30"/>
</dataset>

要注意,如果數據庫中某一條字段為null,在flat XML中將不會顯示該attribute。另外,FlatXmlDataSet用XML文件中該表的第一行數據來制定表的結構。因此,如果數據庫中某個字段所有記錄都為null,或者恰巧第一條記錄為null,那么得到的表結構與原數據庫的表結構就不一致了,測試就會失敗。FlatXmlDataSet中存在一個column sensing的概念,在從文件加載數據時,將該屬性設置為true,就會根據第一行展現出來的表結構,自動將別的行的列補齊。

XmlDataSet對應的XML文件里,用元素的子元素對應表的列。如:

<dataset>
    <Person>
        <Name>Kirin</Name>
        <Age>31</Age>
        <Location>Beijing</Location>
    </Person>
    <Person>
        <Name>Jade</Name>
        <Age>30</Age>
        <Location/>
    </Person>
</dataset>

null用空元素來表示。

將數據庫導出到XML文件

我們可以手寫XML來准備數據,也可以從數據庫中導出現有的數據,用FlatXmlDataSet.write()靜態方法即可,例如:

QueryDataSet dataSet = new QueryDataSet(connection);
dataSet.addTable(TABLE_NAME, "select * from " + TABLE_NAME);
dataSet.addTable(...);
FlatXmlDataSet.write(dataSet, new FileOutputStream("data.xml"));

重寫getDataSet

有了文件數據,我們就需要重寫getDataSet(),讓它加載文件中的數據並返回。

@Override
protected IDataSet getDataSet() throws Exception {
    // set column sensing as true, so it can dynamically and columns with null value. 
    return new FlatXmlDataSetBuilder()                  
                .setColumnSensing(true)
                .build(new FileInputStream("data.xml"));
}

IDatabaseTester

DBTestCase重寫了getConnection(),並把它設置為final,將獲取connection的操作委托給IDatabaseTester,我們可以通過重寫getDatabaseTester()方法來設置具體的IDatabaseTester。Dbunit中,IDatabaseTester的實現類一共有四個:

  • DefaultDatabaseTester
  • JdbcDatabaseTester
  • DataSourceDatabaseTester
  • JndiDatabaseTester

它們的用途不言自明。

DatabaseTestCase重寫了TestCase里的setUp()tearDown()方法。

protected void setUp() throws Exception
{
    super.setUp();
    final IDatabaseTester databaseTester = getDatabaseTester();
    assertNotNull( "DatabaseTester is not set", databaseTester );
    databaseTester.setSetUpOperation( getSetUpOperation() );
    databaseTester.setDataSet( getDataSet() );
    databaseTester.setOperationListener(getOperationListener());
    databaseTester.onSetup();
}

protected void tearDown() throws Exception
{
    try {
        final IDatabaseTester databaseTester = getDatabaseTester();
        assertNotNull( "DatabaseTester is not set", databaseTester );
        databaseTester.setTearDownOperation( getTearDownOperation() );
        databaseTester.setDataSet( getDataSet() );
        databaseTester.setOperationListener(getOperationListener());
        databaseTester.onTearDown();
    } finally {
        tester = null;
        super.tearDown();
    }
}

可以看出它們的大體意圖:為tester設置操作、數據集和監聽器,然后執行相應的操作。獲取數據集的是抽象方法,需要我們來實現。監聽器主要負責在得到數據連接或setUp、tearDown結束后執行的操作,使用默認實現即可。我們主要來說說getSetUpOperationgetTearDownOperation返回的DatabaseOperation

DatabaseOperation

DatabaseOperation定義了對數據庫進行的操作,它是一個抽象類,通過靜態字段提供了幾種內置的實現:

  • NONE:不執行任何操作,是getTearDownOperation的默認返回值。
  • UPDATE:將數據集中的內容更新到數據庫中。它假設數據庫中已經有對應的記錄,否則將失敗。
  • INSERT:將數據集中的內容插入到數據庫中。它假設數據庫中沒有對應的記錄,否則將失敗。
  • REFRESH:將數據集中的內容刷新到數據庫中。如果數據庫有對應的記錄,則更新,沒有則插入。
  • DELETE:刪除數據庫中與數據集對應的記錄。
  • DELETE_ALL:刪除表中所有的記錄,如果沒有對應的表,則不受影響。
  • TRUNCATE_TABLE:與DELETE_ALL類似,更輕量級,不能rollback。
  • CLEAN_INSERT:是一個組合操作,是DELETE_ALL和INSERT的組合。是getSetUpOeration的默認返回值。

由此我們可以總結出,在一個測試執行前后,DbUnit會為我們做哪些工作:

  1. 移除數據庫中的所有記錄(CLEAN_INSERT中的DELETE_ALL)。
  2. 將數據集中的數據加載到數據庫中(CLEAN_INSERT中的INSERT)。
  3. 運行測試。
  4. 測試運行完畢后,不執行任何操作。

我們可以根據需要,在測試類中重寫setUptearDown,以實現定制的需求。比如,數據庫中已經有一些數據,我們不希望數據集中的數據對它們產生任何影響,這時可以先將數據庫中的數據備份到內存中,等測試完成后再恢復到數據庫中,代碼如下:

private IDataSet dataSetBackup;
private static final String[] TABLE_NAMES = new String[] { "..." };

@Override
protected void setUp() throws Exception {
    dataSetBackup = new CachedDataSet(getConnection().createDataSet(TABLE_NAMES));
    super.setUp();      
}

@Override
protected void tearDown() throws Exception {
    try {
        final IDatabaseTester databaseTester = getDatabaseTester();
        assertNotNull( "DatabaseTester is not set", databaseTester );
        databaseTester.setTearDownOperation( getTearDownOperation() );
        databaseTester.setDataSet( dataSetBackup ); // 這里不使用getDataSet(),而是使用備份的數據庫中數據
        databaseTester.setOperationListener(getOperationListener());
        databaseTester.onTearDown();
    } finally {
        tester = null;
        dataSetBackup = null;
        //super.tearDown(); // 這里不再調用基類的tearDown
    }
}

@Override
protected DatabaseOperation getTearDownOperation() {
    return DatabaseOperation.CLEAN_INSERT;
}

測試前用CLEAN_INSERT,是用數據集覆蓋數據庫,測試后用CLEAN_INSERT,使用備份的數據庫覆蓋之前插入到數據庫中的數據集。

完整的基類代碼在這里

好了,現在可以開始測試了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM