淺析數據庫程序的單元測試

發表于:2007-05-16來源:作者:點擊數: 標簽:單元測試數據庫測試單元程序
本文是作者在進行數據庫 功能測試 時的一些心得,其中的例子是用java語言編寫的,但我認為這些想法對于大多數編程環境都普遍適用。當然,我仍致力于尋找更佳的 解決方案 。 現實的問題是這樣的:你有一個 SQL 數據庫,一些存儲過程和一個介于應用程序和數據
本文是作者在進行數據庫功能測試時的一些心得,其中的例子是用java語言編寫的,但我認為這些想法對于大多數編程環境都普遍適用。當然,我仍致力于尋找更佳的解決方案。

現實的問題是這樣的:你有一個SQL數據庫,一些存儲過程和一個介于應用程序和數據庫之間的中間層。你怎樣在其中插入測
試代碼從而保證在數據庫中數據存取功能的實現?

為什么會有這樣的問題?


我猜想有些,可能不完全是大多數的數據庫開發過程都是這樣的:建立數據庫,編寫存取數據到數據庫的代碼,編譯并運行,用一個查詢語句查驗所列的數據是否正確顯示。如果能正確顯示那就大功告成了。

然而,這種靠眼睛來檢測的弊端在于:你不經常進行這樣的檢驗,而且這種檢驗是不完全的。存在這樣的可能性,當你對系統進行了修改,過了幾個月后,你無意中破壞了系統,從而導致數據的丟失。作為一個編程人員,你可能不會花很多時間來檢查數據本身,這就使錯誤的數據要經過較長的時間才能暴露出來。我曾經參與一個建立網站的項目,該項目中在注冊時有一個必填數據在大半年中沒有被發現未實際輸入進數據庫。盡管公司市場部曾經提出他們需要這一信息,但因為這項數據從來沒有人去看它,直接導致了這一問題在很長時間內沒有被發現。

自動化測試,由于它能經常測試而且測試范圍較廣,降低了數據丟失的風險。我發現它能使我更心安理得地休息。當然,自動化測試還有其他一些好處,他們本身就是代碼編寫的范例,也可以作為文檔,便于你修改別人編寫的原始程序,從而減少檢測所需的時間。

什么是我們所談論的測試?


設想有一個非常簡單的用戶數據庫,包括用戶電子信箱和一個標志,用來指示郵件地址是否被彈回。你的數據庫程序應該包括插入、修改、刪除和查詢等方法

插入方法會調用一個存儲過程將數據寫入數據庫。為了敘述方便,這里省去了一些細節,大致的程序如下所示:

public class UserDatabase 
{ 
... 
public void insert(User user) 
{ 
PreparedStatement ps = connection.prepareCall("
{ call User_insert(?,?) }"); 
ps.setString(1, user.getEmail()); 
ps.setString(2, user.isBad()); 
// In real life, this would be a boolean. 
ps.executeUpdate(); 
ps.close(); 
} 
... 
}


而我認為的測試代碼應為:

public class TestUserDatabase extends TestCase 
{ 
... 
public void testInsert() 
{ 
// Insert a test user: 
User user = new User("some@email.address"); 
UserDatabase database = new UserDatabase(); 
database.insert(user); 
// Make sure the data really got there: 
User db_user = database.find("some@email.address"); 
assertTrue("Expected non-null result",
 db_user != null); 
assertEquals("Wrong email",
 "some@email.address", db_user.getEmail()); 
assertEquals("Wrong bad flag", false, db_user.isBad()); 
} 
... 
}


可能你還有更多測試代碼。(注意一些測試,例如對date類的測試)。

assertTrue和assertEquals方法進行條件測試。如果測試失敗,他們將返回診斷消息。其重點是這些測試都基于一個測試框架自動執行,并給出測試成敗的標志。這些測試都基于用java語言編寫的測試框架Junit類(程序附后)。這一框架也能適應其他諸如C, C++, Perl, Python, .NET (all languages), PL/SQL, Eiffel, Delphi, VB等語言環境。

下一個問題就是:我們有測試,但我們怎樣保證測試數據和實際數據能嚴格區分?

不同的鑒別方法


在開始之前,我必須指出你最好有一個測試用的數據庫,你可能更想在非正式的數據庫中實踐我講的東西。

第一種方法是手工在數據庫中輸入一些預先知道的測試性數據,例如在郵件地址中輸入“testuser01@test.testing”。如果你正在測試數據庫的查詢功能,你能預先知道,比如說有五個,數據庫記錄是以“@test.testing”結尾的。

由以上方式插入的數據必須由測試本身進行必要的維護。例如,測試必須負責刪除所建立的測試數據,而避免對實際數據進行操作,從而保證整個數據庫處于完好狀態。

這種方法還是存在以下問題:

你不得不和其他編程人員進行數據協調——假設他們也有他們自己的測試數據庫。

在數據庫中有些特殊的數據并不正確,如一些特別的郵件地址和被保留餓編號前綴。

在某些情況下,你將不能用一些特殊的數據來區分測試數據和實際數據,這就比較棘手。例如,某條數據由一些整數型字段構成,而作為測試用的數值都看起來較為合理。

你的測試只限于你為測試所保留的某些特殊值,這意味著你將小心地選擇那些特殊值。

如果數據對時間敏感,那對數據庫的維護將更為困難。例如,數據庫中有產品銷售提議,而該提議只在明確的時間段里有效。

我曾經試著做過修改。例如,在數據庫中增加“is_test”字段作為區分測試數據的標志,從而避免特殊值的問題。但由此帶來的問題是,你的測試代碼將只測試那些標記為測試的數據,而你的正式代碼卻要處理那些未標記為測試的數據。如果你的測試在這方面有區別,你事實上并不在測試同一代碼。

你需要四個數據庫


有些想法認為一個好的測試是足夠充分的并能建立測試所需要的全部數據。如果你能在測試進行前就明確知道數據庫所處的狀態,測試可以進行一些簡化。一個簡化的方法是建立一個獨立的單元測試數據庫用于測試程序,測試程序在開始進行前清除測試數據庫中的全部數據。

在代碼中,你可以編寫一個dbSetUp方法,如下所示:

public void dbSetUp() 
{ 
// Put the database in a known state: 
// (stored procedures would probably be better here) 
helper.exec("DELETE FROM SomeSideTable"); 
helper.exec("DELETE FROM User"); 
// Insert some commonly-used test cases: 
... 
}


任何數據庫測試程序都將在做任何事前首先調用dbSetUp方法,它將使測試數據庫處于一種已知狀態(大部分情況下是空數據庫狀態)。這種做法具有以下的優點:

所有的測試數據都在代碼層和其他編程人員進行交流,因此沒有必要進行外部測試數據協調。

無須測試用的特殊數據的介入。

簡單而容易理解的一種方法。

在每一次測試前刪除和插入數據可能會花較多時間,但是由于測試用的數據量相對較小,我認為這種方法比較快捷,特別是在測試一個本地數據庫時。

這種做法不利的一面是你需要至少兩個數據庫。但是請記住,他們在必要是都可以在同一個服務器上運行。采用這種方法,我用了四個數據庫,另外兩個在緊急關頭時使用,具體如下:

1. 實際使用數據庫,包含實際數據。在這個數據庫中不進行測試,確保數據的完整性。

2. 你的本地開發數據庫,用來進行大部分的測試。

3. 一個加入一定量數據的本地開發數據庫,可能和其他編程人員共享,用來運行應用程序并檢測是否能在實際使用的數據庫上運行,而不是照搬實際使用數據庫中的全部數據。從嚴格意義上說你可能并不需要這一數據庫,但這一數據庫能確保應用程序在有大量數據的數據庫中順利運行。

4. 一個發布數據庫,或稱集成數據庫,用來在正式發布前進行一系列測試,從而確保對所有本地數據庫的修改都得到確認。如果你一個人開發,你可以省略這個數據庫,但你必須確保所有對數據結構和存儲過程的修改都在實際使用數據庫中得到確認。

在有多個數據庫的情況下,你要確保不同數據庫間結構的同步:如果你在測試數據庫中改變表的定義或存儲過程,你必須記得在實際使用的服務器上進行同樣的修改。發布數據庫的作用就是提醒你進行這些修改。另外,我發現如果代碼控制系統能將提交時的注釋用郵件形式自動發給整個開發組,那將給團隊開發帶來較大幫助。CVS就能做到這一點,我希望你能利用這一功能。

在合適的數據庫中進行測試


在這種情況下,你必須連接正確的數據庫。在實際使用數據庫中進行測試有可能刪除所有的有用數據,這點令我十分害怕。

有幾種辦法能避免此類悲劇的發生。例如,比較普遍的做法是將數據庫連接設置記錄在初始文件中,從而明確哪一個是測試數據庫。你也可以通過初始文件進行本地數據庫的測試,而用其他指定方法連接實際使用數據庫。

在java代碼中,初始文件可能如下所示;

myapp.db.url=jdbc:mysql://127.0.0.1/mydatabase


這一連接字符串用來連接數據庫。你可以添加第二個連接字符串來區分測試數據庫:

myapp.db.url=jdbc:mysql://127.0.0.1/mydatabase 
myapp.db.testurl=jdbc:mysql://127.0.0.1/my_test_database


在測試代碼中,你可以檢查并確保在連接到測試數據庫后應用程序才能繼續運行:

public void dbSetUp() 
{ 
String test_db = InitProperties.get("myapp.db.testurl"); 
String db = InitProperties.get("myapp.db.url"); 
if (test_db == null) 
abort("No test database configured"); 
if (test_db.equals(db)) 
{ 
// All is well: the database we're connecting to is the 
// same as the database identified as "for testing" 
} 
else 
{ 
abort("Will not run tests against a non-test database"); 
} 
}


另一個技巧是,如果你有一個本地測試數據庫,測試程序能通過提供IP地址或主機名進行檢測。如果不是“localhost/127.0.0.1”,這就有連接在實際使用數據庫上進行測試的風險。

關于測試日期的體會


如果你想存儲日期信息,你大概想確認你存的日期信息是否正確。請注意以下幾點。

首先先問自己,是誰創建該日期。如果是你的應用程序,那驗證比較簡單,因為你可以通過查看數據庫中的具體日期進行比較。如果是數據庫本身創建該日期,可能作為一個缺省字段,那你可能就會有些問題。例如,你能確保你代碼所代表的時區和數據庫的時區一致嗎?從沒有聽說過數據庫是以格林尼治標準時間為準顯示時間和日期的。你能確保運行應用程序的計算機上的時間和數據庫所在計算機上的時間保持一致嗎?如果不是,你必須在進行時間的比較時留出一定的誤差。

如果你遇到這些情況,有些事是你可以做的:

如果你預先知道所用的時區,在測試前將所有日期和時間全部轉換成那個時區的日期和時間。

在比較時間時設立一定的誤差,比如說幾分鐘、幾小時或幾個月??瓷先ト狈φf服力,但至少它能捕獲諸如日期為空或1970年1月1日等錯誤。

總結


在本文中,我想說:

單元數據庫測試是一件值得做的事;

如果你能給一系列測試程序一個對應的數據庫,測試本身并不非??膳?。

還有其他方法能解決這一問題。我還不能確信模仿對象(Mock Object)這一方法。就我對這一方法的理解,模仿對象模擬了一個系統中間層(在本文中,是數據庫操作系統),使得模仿的數據庫總能返回你想要的數據。我比較欣賞這一概念,它鼓勵你對測試進行分層,可能劃分成SQL方面的測試和Java語言方面的測試,從而對模仿的ResultSet對象進行測試。

我比較關注那些能導致一次能使兩個或兩個以上的數據表產生變化的操作。在這種情況下,用模仿對象方法進行數據庫的維護和實現比較困難。當然,我還要找到一種好方法進行數據庫中SQL方面的測試,從而確認數據被正確地存儲到數據庫中。

原文轉自:http://www.anti-gravitydesign.com

国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97