現實系統中通常會有一些具有外部依賴性的對象,這些對象和數據庫或者其他對象存在諸多關聯。如果我們對這樣的對象編寫單元和組件級測試的話,可以想象將是非常麻煩的一件事.因為這種外部依賴性的存在,使的我們很難將對象孤立出來進行測試。
經常提及的白盒測試法,基本上就是通過控制對象的外部依賴性來達到隔離對象的目的,使的可以操作這些對象的狀態和相關行為。
運用 模擬對象(mock objects)
或者stubs,就是一個控制對象外部依賴性的解決方案。通過隔離那些關聯的數據庫訪問類,象JDBC的相關操作類,對于控制對象外部依賴性將是很有效的。但模擬對象的解決方案對一些特殊的應用系統架構就顯得力不從心了,象那些運用了EJB的CMP(container-managed
persistence)或者 JDO(java Data
Objects)的應用系統架構,在這些架構里,數據庫的訪問對象是在最底層的而且很隱蔽。
由Manuel Laflamme
編寫的開放源代碼的DBUnit架構體系,對于控制系統內部的數據庫依賴性提供了一個非常不錯的解決方案。他允許程序員在整個的測試過程中自由的管理控制數據庫的狀態,這很重要。利用DBUnit,在測試之前,我們可以給目標數據庫植入我們需要的數據集,而且,在測試完畢后,數據庫完全能夠回溯到測試前的狀態。
在很多成功的軟件項目中,測試自動化往往是關鍵的層面。DBUnit允許開發人員創建測試用例代碼,在這些測試用例的生命周期內我們可以很好的控制數據庫的狀態。而且,這些測試用例是很容易實現自動化的。這樣在測試過程中我們無須對它進行人工的干預,為人工干預造成的后果而擔心就更沒必要了。
簡單介紹
配置使用DBUnit的第一步我們首先需要知道如何生成數據庫schema,這個文件是XML格式的,其中包括了數據庫的表及相關數據信息。
例如,這里有一個數據庫表employee
,我們可以用SQL的形式這樣將他表示出來。
而且,我們可以看到,一個簡單的數據集可以這樣表示
在DBUnit中,上面這個表和抽樣數據信息可以用XML文件的形式這樣表示:
<EMPLOYEE employee_uid='1'
start_date='2001-11-01'
first_name='Andrew'
ssn='xxx-xx-xxxx'
last_name='Glover' />
這個生成的XML格式的文件可以作為系統所需的所有種子文件(seed
files)的樣本模版.
為相互關聯的測試場景創建多個種子文件是一個很有效的策略,就象通過不同的數據庫文件來區分隔離數據庫狀態是一個道理。多種子文件策略可以將我們的測試目標鎖定到較小的范圍,目標數據可以只針對數據庫的表,而不是整個數據庫。
為了給目標數據庫植入不同的職員記錄,我們需要的XML數據文件如下所示:
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<EMPLOYEE employee_uid='1'
start_date='2001-01-01'
first_name='Drew' ssn='000-29-2030'
last_name='Smith' />
<EMPLOYEE employee_uid='2'
start_date='2002-04-04'
first_name='Nick' ssn='000-90-0000'
last_name='Marquiss' />
<EMPLOYEE employee_uid='3'
start_date='2003-06-03'
first_name='Jose' ssn='000-67-0000'
last_name='Whitson' />
</dataset>
現在,要讓DBUnit和我們所需的數據庫schema一起工作了,對于程序員來說,我們使用DBUnit進行測試可以有兩種選擇:通過直接編碼方式進行測試或者與Ant結合.
編碼方式
DBUnit框架提供了一個基本的抽象測試用例類,叫做DatabaseTestCase,它是JUnit框架中的基礎類TestCase的子類。如果我們使用這個類必須首先實現兩個鉤子方法(hook
methods):getConnection()和getDataSet().
方法getConnection()需要返回一個IDatabaseConnection類型的對象,這個對象是一個基于普通JDBC連接的包裝類。例如,下面的代碼段演示了在MySQL數據庫環境下,IDatabaseConnection類型連接對象的創建方法。
protected IDatabaseConnection getConnection()
throws Exception {
Class driverClass = Class.forName("org.gjt.mm.mysql.Driver");
Connection jdbcConnection = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1/hr", "hr", "hr");
return new DatabaseConnection(jdbcConnection);
}
方法getDataSet()返回一個IDataSet類型對象,其實,說白了,他就是我們先前提到的XML數據的種子文件的另一種表現形式。
protected IDataSet getDataSet() throws Exception {
return new FlatXmlDataSet(
new
FileInputStream("hr-seed.xml"));
}
有了這兩個基本的方法以后,DBUnit就可以按照它預先缺省的行為工作了。DatabaseTestCase類提供了兩個fixture(我叫它固件,不知仁兄同意否?)方法來控制測試前和測試后的數據庫狀態。這兩個方法就是:
getSetUpOperation() 和 getTearDownOperation().
一種高效的實施方案就是讓getSetUpOperation()方法執行REFRESH操作,通過這個操作,我們可以用種子文件中的數據去更新目標數據庫里的數據。接下來,就是getTearDownOperation(),讓他去執行一個NONE操作,也就是什么也不執行。
protected DatabaseOperation getSetUpOperation()
throws
Exception {
return DatabaseOperation.REFRESH;
}
protected DatabaseOperation getTearDownOperation()
throws
Exception {
return DatabaseOperation.NONE;
}
還有一種有效的方法就是在getSetUpOperation()方法中執行CLEAN_INSERT操作,這樣首先會將目標數據庫中與我們提供的種子文件一致的數據刪除,然后將我們提供的數據插入到數據庫中。這個實施順序保證了我們對數據庫的精確控制。
代碼樣例
在一個基于J2EE的人力資源系統中,我們很希望對某個數據操作周期實現測試自動化,這個操作周期包括職員的新增,檢索,更新和刪除。遠程接口定義了下列的業務方法(為了簡潔清楚,省略了方法中的throws子句).
//譯者注:這里的EmployeeValueObject類型對象,譯者認為是代表職員實體信息的對象。
public void createEmployee( EmployeeValueObject emplVo )
public EmployeeValueObject getEmployeeBySocialSecNum( String ssn )
public void updateEmployee( EmployeeValueObject emplVo )
public void deleteEmployee( EmployeeValueObject emplVo )
測試getEmployeeBySocialSecNum()方法
需要植入一條數據到目標數據庫中,另外,測試deleteEmployee()方法和updateEmployee()方法時,同樣也是在先前植入的這條記錄的基礎上進行。最后,測試類會首先利用createEmployee()方法創建一條記錄,同時我們需要校驗執行這個方法時,是否會有異常發生。
下面這個DBUnit種子文件,叫做"employee_hr_seed.xml",下面將用到這個文件。
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<EMPLOYEE employee_uid='1'
start_date='2001-01-01'
first_name='Drew' ssn='333-29-9999'
last_name='Smith' />
<EMPLOYEE employee_uid='2'
start_date='2002-04-04'
first_name='Nick' ssn='222-90-1111'
last_name='Marquiss' />
<EMPLOYEE employee_uid='3'
start_date='2003-06-03'
first_name='Jose' ssn='111-67-2222'
last_name='Whitson' />
</dataset>
測試類 EmployeeSessionFacadeTest
,需要擴展DBUnit的基礎類DatabaseTestCase并且必須提供對getConnection()和getDataSet()方法的實現,在getConnection()方法中將獲得與EJB容器初始化時一樣的數據庫實例,getDataSet()方法負責讀取上面提及的employee_hr_seed.xml文件的數據。
測試方法相當簡單,因為DBUnit已經為我們處理了復雜的數據庫生命周期任務。為了測試getEmployeeBySocialSecNum()方法,只需要簡單的傳遞一個存在于種子文件中的社保代碼號即可,比如
"333-29-9999".
//譯者注:EmployeeFacade 類型對象,譯者認為是代表底層數據庫數據的映射體
public void testFindBySSN() throws Exception{
EmployeeFacade facade = //obtain somehow
EmployeeValueObject vo =
facade.getEmployeeBySocialSecNum("333-29-9999");
TestCase.assertNotNull("vo shouldn't be null", vo);
TestCase.assertEquals("should be Drew",
"Drew", vo.getFirstName());
TestCase.assertEquals("should be Smith",
"Smith", vo.getLastName());
}
為了確保操作周期中的創建職員方法createEmployee()沒有問題,我們只需簡單的執行一下這個方法,然后校驗一下看有沒有異常拋出,另外,下一步我們要做的就是在這條新增的記錄上進行查找操作,看是否可以找到剛創建的記錄。
public void testEmployeeCreate() throws Exception{