V模型分析<1>
在本文中我要把V模型作為不好的模型的典型來進(jìn)行分析。選擇V模型作為分析的典型是因為V模型是最廣為人知的測試模型。
最典型的V模型版本一般會在其開始部分對軟件開發(fā)過程進(jìn)行描述,如下圖所示:
V模型的各級開發(fā)階段
這是古老的瀑布模型。作為開發(fā)模型,它有很多問題,不過這里不作討論。盡管它的各種狀態(tài)是我們接著要討論的大家最熟悉的V模型的基礎(chǔ)。我的批評意見同時也針對其它的裝飾在一些更好的開發(fā)模型之上的測試模型,例如螺旋模型[Boehm88]。
在V模型中,測試過程被加在開發(fā)過程的后半部分,如下圖所示:
V模型示意圖
單元測試所檢測代碼的開發(fā)是否符合詳細(xì)設(shè)計的要求。集成測試所檢測此前測試過的各組成部分是否能完好地結(jié)合到一起。系統(tǒng)測試所檢測已集成在一起的產(chǎn)品是否符合系統(tǒng)規(guī)格說明書的要求。而驗收測試則檢測產(chǎn)品是否符合最終用戶的需求。
對于測試設(shè)計,顯而易見的是,V模型的用戶往往會把執(zhí)行測試與測試設(shè)計分開對待。在開發(fā)文檔準(zhǔn)備就緒后,就可以開始進(jìn)行相關(guān)的測試設(shè)計。如下圖所示,相應(yīng)的測試設(shè)計覆蓋在了相關(guān)的開發(fā)過程之上:
將測試設(shè)計覆蓋了開發(fā)過程后的V模型
V模型有著很吸引人的對稱外形,并且把很多人都帶入了歧途。本文將集中討論它在單元測試和集成測試中引起的問題。
為了說明的方便,這里專門制作了以下圖片,圖中包括一個單獨(dú)的單元,以及一個單元組,我稱之為子系統(tǒng)(subsystem)。
一個假想的子系統(tǒng)
對于一個單元應(yīng)該多大才最為合適的問題,已經(jīng)有過很多的討論,究竟一個單元僅僅是一個函數(shù),一個類,還是相關(guān)的類的集合?這些討論并不影響我在這里所要闡述的觀點(diǎn)。我們權(quán)且認(rèn)為一個單元就是一個最小程度的代碼塊,開發(fā)人員可以對進(jìn)行獨(dú)立地討論。
V模型認(rèn)為人們首先應(yīng)該對每一個單元進(jìn)行測試。當(dāng)子系統(tǒng)中所有的單元都已經(jīng)測試完畢,它們將被集中到一起進(jìn)行測試,以驗證它們是否可以構(gòu)成一個可運(yùn)行的整體。
那么,如何針對單元進(jìn)行測試呢?我們會查看在詳細(xì)設(shè)計中對接口的定義,或者查看源代碼,或者同時對兩者進(jìn)行查看,找出符合某些測試設(shè)計中的有關(guān)準(zhǔn)則的輸入數(shù)據(jù)來進(jìn)行輸入,然后檢查結(jié)果,看其是否正確。由于各單元一般來說不能獨(dú)立地運(yùn)行,所以我們不得不另外設(shè)計樁模塊(Stub)和驅(qū)動模塊(Driver),如下圖所示。
單元及其外部的驅(qū)動模塊和樁模塊
圖中的箭頭代表了測試的執(zhí)行軌跡。這就是大多數(shù)人所說的“單元測試”。我認(rèn)為這樣的方法有時候是一種不好的方法。
同樣的輸入也可以有同一子系統(tǒng)中的其它單元來提供,這樣,其它的單元既扮演了樁模塊,又扮演了驅(qū)動模塊。如下圖所示:
子系統(tǒng)內(nèi)部各單元間的測試執(zhí)行軌跡
到底選擇哪一種方法,這需要一種折衷和權(quán)衡。設(shè)計樁模塊和驅(qū)動模塊要付出多少代價?這些模塊如何進(jìn)行維護(hù)?子系統(tǒng)是否會由此而掩蓋了一些故障?在整個子系統(tǒng)范圍內(nèi)進(jìn)行排錯的困難程度有多大?如果我們的測試直到集成測試時才真正開始,那么一些bug可能較晚才被發(fā)現(xiàn)。由此造成的代價同設(shè)計樁模塊和驅(qū)動模塊的代價如何比較?
V模型分析<2>
V模型沒有去考慮這些問題,當(dāng)單元開發(fā)完成后就執(zhí)行單元測試,而當(dāng)自系統(tǒng)被集中在一起后就執(zhí)行集成測試,而已。令我奇怪和沮喪的是,人們從不去做一些權(quán)衡,他們已經(jīng)受制于他們的模型。
因此,一個有用的模型應(yīng)該允許測試人員考慮節(jié)省并推遲測試的可能性。
一個測試,如果要發(fā)現(xiàn)一個特定的單元中的bug,是在該單元保持獨(dú)立的情況下執(zhí)行,并且在其外部輔以特定的樁模塊和驅(qū)動模塊。而另一種方法則是讓它作為子系統(tǒng)的一部分來進(jìn)行測試,該測試的設(shè)計主要是為了發(fā)現(xiàn)集成的問題。由于一個子系統(tǒng)本身也需要樁模塊和驅(qū)動模塊來模擬該子系統(tǒng)和其它子系統(tǒng)的聯(lián)系,因此,單元測試和集成測試可能被推遲到至少整個系統(tǒng)已經(jīng)部分集成的時候。在這種情況下,測試者可能通過產(chǎn)品的外部接口同時進(jìn)行單元測試、集成測試和系統(tǒng)測試,同樣的,其主要目的還是為了減少總體生命周期的成本,對測試成本和延期進(jìn)行測試及由此造成延期發(fā)現(xiàn)bug的代價成本進(jìn)行權(quán)衡。據(jù)此而言,“單元測試”、“集成測試”和“系統(tǒng)測試”的區(qū)別已經(jīng)大大削弱了。其結(jié)果可參考下圖:
新的方法:在部分階段延遲進(jìn)行單元測試和集成測試
在上圖右邊的方塊中,要改成為“執(zhí)行某些適當(dāng)?shù)臏y試并得到相應(yīng)的結(jié)果”。
圖中的左邊會怎樣?考慮一下系統(tǒng)測試設(shè)計,它的主要根據(jù)和信息來源是是規(guī)格說明。假設(shè)你知道有2個單元處在一個特定的子系統(tǒng)中,它們在運(yùn)行時相互聯(lián)系,并且要執(zhí)行規(guī)格說明中的一個特定的聲明。為什么不在該子系統(tǒng)被集成時立即對此規(guī)格說明中的聲明進(jìn)行測試,就象是在設(shè)計完成后立即開始測試的設(shè)計一樣呢?如果該聲明的執(zhí)行和子系統(tǒng)外的子系統(tǒng)沒有任何關(guān)系,為什么還要等到整個系統(tǒng)完成以后再測試呢?難道越早發(fā)現(xiàn)bug成本越低不對嗎?
在上一張圖片中,我們用了向上指的箭頭(更有效,但在時間上有延遲)。這里還可以把箭頭往下指(在時間上提前):
新的方法:在不同階段上提前進(jìn)行測試設(shè)計
在這種情況下,左邊的方塊中被標(biāo)記為:“在當(dāng)前信息條件和情況下可以做的任何測試設(shè)計”。這樣,當(dāng)測試設(shè)計得自于系統(tǒng)中某一個組件的描述時,模型必須允許這樣的測試在組件被裝配之前被執(zhí)行。我必須承認(rèn)我的圖片非常難看,這些箭頭指得到處都是,對此我有2點(diǎn)說明:
1. 我們所討論的事情不是創(chuàng)造美,而是想要發(fā)現(xiàn)盡可能多的嚴(yán)重錯誤,同時盡可能地降低成本。
2. 難看的部分原因也是因為必須按照某些次序來執(zhí)行的結(jié)果,亦即開發(fā)人員先提供系統(tǒng)描述文檔,然后測試和這些文檔進(jìn)行關(guān)聯(lián)。這些文檔就象是堅實(shí)的老橡樹,而測試設(shè)計則象是細(xì)細(xì)的枝條纏繞在樹上。如果我們采用不同的原理來進(jìn)行組織,圖片可能就會變得好看些。但復(fù)雜性仍不可避免,因為我們要討論的問題本身就很復(fù)雜。
V模型失敗的原因是它把系統(tǒng)開發(fā)過程劃分為具有固定邊界的不同階段,這使得人們很難跨過這些邊界來采集測試所需要的信息。有些測試應(yīng)該執(zhí)行得更早些,有些測試則需要延后進(jìn)行。而且,它也阻礙了你從系統(tǒng)描述的不同階段中取得信息進(jìn)行綜合。例如,某些組織有時執(zhí)行這樣的做法,即對完成的工作進(jìn)行簽署。這樣的規(guī)定也擴(kuò)展到系統(tǒng)測試的設(shè)計。簽署表示已經(jīng)過評估,該測試設(shè)計工作已經(jīng)完成,除非對應(yīng)的設(shè)計文檔改變,否則就不會被修訂。如果同這些測試相關(guān)的信息后來被重新挖掘和認(rèn)識,例如,架構(gòu)設(shè)計表明有些測試是多余的,或者,詳細(xì)設(shè)計表明有一個內(nèi)部的邊界可以和已存在的系統(tǒng)測試組合在一起進(jìn)行測試的話,那么實(shí)際上還需要繼續(xù)調(diào)整原來的系統(tǒng)測試設(shè)計。
因此,模型必須允許利用不同來源的綜合信息進(jìn)行個別的測試設(shè)計。另外,模型還應(yīng)該允許在新的信息來源出現(xiàn)后重新進(jìn)行測試的設(shè)計。

