明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

數(shù)據(jù)庫程序的單元測試(ZT)

[摘要]數(shù)據(jù)庫程序的單元測試本文來自互聯(lián)網(wǎng),原作者不詳。翻譯:PMTeamXu NingNing這些筆錄是我關(guān)于已完成的數(shù)據(jù)庫功能測試的一些心得。其中的例子是用java語言編寫的,但我認(rèn)為這些想法對于大多數(shù)編程環(huán)境都普遍適用。當(dāng)然,我仍致力于尋找更佳的解決方案,F(xiàn)實(shí)的問題是這樣的:你有一個(gè)SQL數(shù)據(jù)庫,一...
數(shù)據(jù)庫程序的單元測試

本文來自互聯(lián)網(wǎng),原作者不詳。

翻譯:PMTeamXu NingNing



這些筆錄是我關(guān)于已完成的數(shù)據(jù)庫功能測試的一些心得。其中的例子是用java語言編寫的,但我認(rèn)為這些想法對于大多數(shù)編程環(huán)境都普遍適用。當(dāng)然,我仍致力于尋找更佳的解決方案。

現(xiàn)實(shí)的問題是這樣的:你有一個(gè)SQL數(shù)據(jù)庫,一些存儲(chǔ)過程和一個(gè)介于應(yīng)用程序和數(shù)據(jù)庫之間的中間層。你怎樣在其中插入測試代碼從而保證在數(shù)據(jù)庫中數(shù)據(jù)存取功能的實(shí)現(xiàn)?



一、為什么會(huì)有這樣的問題?

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

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

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





二、什么是我們所談?wù)摰臏y試?



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

插入方法會(huì)調(diào)用一個(gè)存儲(chǔ)過程將數(shù)據(jù)寫入數(shù)據(jù)庫。為了敘述方便,這里省去了一些細(xì)節(jié),大致的程序如下所示:

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();

}

...

}



而我認(rèn)為的測試代碼應(yīng)為:

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方法進(jìn)行條件測試。如果測試失敗,他們將返回診斷消息。其重點(diǎn)是這些測試都基于一個(gè)測試框架自動(dòng)執(zhí)行,并給出測試成敗的標(biāo)志。這些測試都基于用java語言編寫的測試框架Junit類(程序附后)。這一框架也能適應(yīng)其他諸如C, C++, Perl, Python, .NET (all languages), PL/SQL, Eiffel, Delphi, VB等語言環(huán)境。

下一個(gè)問題就是:我們有測試,但我們怎樣保證測試數(shù)據(jù)和實(shí)際數(shù)據(jù)能嚴(yán)格區(qū)分?





三、不同的鑒別方法



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

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

由以上方式插入的數(shù)據(jù)必須由測試本身進(jìn)行必要的維護(hù)。例如,測試必須負(fù)責(zé)刪除所建立的測試數(shù)據(jù),而避免對實(shí)際數(shù)據(jù)進(jìn)行操作,從而保證整個(gè)數(shù)據(jù)庫處于完好狀態(tài)。

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

l你不得不和其他編程人員進(jìn)行數(shù)據(jù)協(xié)調(diào)——假設(shè)他們也有他們自己的測試數(shù)據(jù)庫。

l在數(shù)據(jù)庫中有些特殊的數(shù)據(jù)并不正確,如一些特別的郵件地址和被保留餓編號(hào)前綴。

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

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

l如果數(shù)據(jù)對時(shí)間敏感,那對數(shù)據(jù)庫的維護(hù)將更為困難。例如,數(shù)據(jù)庫中有產(chǎn)品銷售提議,而該提議只在明確的時(shí)間段里有效。

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





四、你需要四個(gè)數(shù)據(jù)庫



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

在代碼中,你可以編寫一個(gè)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:

...

}



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

l所有的測試數(shù)據(jù)都在代碼層和其他編程人員進(jìn)行交流,因此沒有必要進(jìn)行外部測試數(shù)據(jù)協(xié)調(diào)。

l無須測試用的特殊數(shù)據(jù)的介入。

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

l在每一次測試前刪除和插入數(shù)據(jù)可能會(huì)花較多時(shí)間,但是由于測試用的數(shù)據(jù)量相對較小,我認(rèn)為這種方法比較快捷,特別是在測試一個(gè)本地?cái)?shù)據(jù)庫時(shí)。

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

1. 實(shí)際使用數(shù)據(jù)庫,包含實(shí)際數(shù)據(jù)。在這個(gè)數(shù)據(jù)庫中不進(jìn)行測試,確保數(shù)據(jù)的完整性。

2. 你的本地開發(fā)數(shù)據(jù)庫,用來進(jìn)行大部分的測試。

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

4. 一個(gè)發(fā)布數(shù)據(jù)庫,或稱集成數(shù)據(jù)庫,用來在正式發(fā)布前進(jìn)行一系列測試,從而確保對所有本地?cái)?shù)據(jù)庫的修改都得到確認(rèn)。如果你一個(gè)人開發(fā),你可以省略這個(gè)數(shù)據(jù)庫,但你必須確保所有對數(shù)據(jù)結(jié)構(gòu)和存儲(chǔ)過程的修改都在實(shí)際使用數(shù)據(jù)庫中得到確認(rèn)。

在有多個(gè)數(shù)據(jù)庫的情況下,你要確保不同數(shù)據(jù)庫間結(jié)構(gòu)的同步:如果你在測試數(shù)據(jù)庫中改變表的定義或存儲(chǔ)過程,你必須記得在實(shí)際使用的服務(wù)器上進(jìn)行同樣的修改。發(fā)布數(shù)據(jù)庫的作用就是提醒你進(jìn)行這些修改。另外,我發(fā)現(xiàn)如果代碼控制系統(tǒng)能將提交時(shí)的注釋用郵件形式自動(dòng)發(fā)給整個(gè)開發(fā)組,那將給團(tuán)隊(duì)開發(fā)帶來較大幫助。CVS就能做到這一點(diǎn),我希望你能利用這一功能。





五、在合適的數(shù)據(jù)庫中進(jìn)行測試



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

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

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

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

這一連接字符串用來連接數(shù)據(jù)庫。你可以添加第二個(gè)連接字符串來區(qū)分測試數(shù)據(jù)庫:

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

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

在測試代碼中,你可以檢查并確保在連接到測試數(shù)據(jù)庫后應(yīng)用程序才能繼續(xù)運(yùn)行:

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");

 }

}



另一個(gè)技巧是,如果你有一個(gè)本地測試數(shù)據(jù)庫,測試程序能通過提供IP地址或主機(jī)名進(jìn)行檢測。如果不是“l(fā)ocalhost/127.0.0.1”,這就有連接在實(shí)際使用數(shù)據(jù)庫上進(jìn)行測試的風(fēng)險(xiǎn)。







六、關(guān)于測試日期的體會(huì)



如果你想存儲(chǔ)日期信息,你大概想確認(rèn)你存的日期信息是否正確。請注意以下幾點(diǎn)。

首先先問自己,是誰創(chuàng)建該日期。如果是你的應(yīng)用程序,那驗(yàn)證比較簡單,因?yàn)槟憧梢酝ㄟ^查看數(shù)據(jù)庫中的具體日期進(jìn)行比較。如果是數(shù)據(jù)庫本身創(chuàng)建該日期,可能作為一個(gè)缺省字段,那你可能就會(huì)有些問題。例如,你能確保你代碼所代表的時(shí)區(qū)和數(shù)據(jù)庫的時(shí)區(qū)一致嗎?從沒有聽說過數(shù)據(jù)庫是以格林尼治標(biāo)準(zhǔn)時(shí)間為準(zhǔn)顯示時(shí)間和日期的。你能確保運(yùn)行應(yīng)用程序的計(jì)算機(jī)上的時(shí)間和數(shù)據(jù)庫所在計(jì)算機(jī)上的時(shí)間保持一致嗎?如果不是,你必須在進(jìn)行時(shí)間的比較時(shí)留出一定的誤差。

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

如果你預(yù)先知道所用的時(shí)區(qū),在測試前將所有日期和時(shí)間全部轉(zhuǎn)換成那個(gè)時(shí)區(qū)的日期和時(shí)間。

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



七、總結(jié)

在本文中,我想說:

單元數(shù)據(jù)庫測試是一件值得做的事;

如果你能給一系列測試程序一個(gè)對應(yīng)的數(shù)據(jù)庫,測試本身并不非常可怕。

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

我比較關(guān)注那些能導(dǎo)致一次能使兩個(gè)或兩個(gè)以上的數(shù)據(jù)表產(chǎn)生變化的操作。在這種情況下,用模仿對象方法進(jìn)行數(shù)據(jù)庫的維護(hù)和實(shí)現(xiàn)比較困難。當(dāng)然,我還要找到一種好方法進(jìn)行數(shù)據(jù)庫中SQL方面的測試,從而確認(rèn)數(shù)據(jù)被正確地存儲(chǔ)到數(shù)據(jù)庫中。