從直觀上說所有真正的檢驗都是試圖給理論“下絆子”:它不但是一場嚴格的考試,而且是一場不近人情的考試——其目的在于使考生們落敗考場,而不是給考生們以機會去展示他們所知道的東西。展示所知的態(tài)度是那些打算去確證或“證實”其理論的人的態(tài)度。
——卡爾?波普爾(Karl Popper)[1]
證實與證偽
人們通常把軟件測試視為一種歸納并推論過程,尤其是在功能性測試(functional testing)方面,我們借助邊界值、等價類、決策表等測試設(shè)計方法,通過設(shè)計與執(zhí)行部分用例來推斷整體。比如,對于一個只接受1~255的輸入,如果按照邊界值的方法,我們選擇檢驗1、2、128、254、255,即可推斷程序是否運行正確。也就是說在這里說我們通過觀察1/51場景的測試結(jié)果,來推論50/51未經(jīng)測試場景的結(jié)果。
這樣的測試必然是不完備的,從邏輯的觀點來看,顯然不能證明從單稱陳述(某個場景沒問題)中推論出全稱陳述(所有場景都沒問題)是正確的。就像我們不能從單稱陳述“昨天太陽從東方升起”或“今天太陽從東方升起”來推論全稱陳述“每一天太陽都從東方升起”一樣。在上例中,實際上我們是可以對255個場景進行完備的測試的,但對于絕大多數(shù)需要測試的軟件系統(tǒng),測試工作者面對的往往是無窮的場景?,F(xiàn)在讓我們考慮一個很簡單的功能:用鼠標點擊屏幕上的一個按鈕,鼠標按鍵松開后系統(tǒng)彈出OK的提示。在不檢查實現(xiàn)代碼的情況下,我們并不能保證在按鈕的不同坐標位置點擊后會出現(xiàn)同樣的結(jié)果,也不能保證從按下到松開的不同延時會出現(xiàn)同樣的結(jié)果,另外,即使是同樣的坐標、同樣的延時,要預(yù)期第一次點擊與第二次點擊會出現(xiàn)同樣的結(jié)果似乎也是武斷的。盡管這些條件看上去有些不切實、甚至帶有詭辯的味道,但從實現(xiàn)的角度來講,這些都是有可能的:因為編程人員完全可以在代碼中人為地設(shè)置一個讓人意想不到的復(fù)雜條件,來觸發(fā)一個后門、或者一個非常規(guī)事件——但它卻是再多的測試都不一定能發(fā)現(xiàn)的。因此,不管是操作步驟與操作環(huán)境的復(fù)雜組合,還是道德風險的可能性,都使得能證明軟件有缺陷的潛在場景已近乎無限大。這也就意味著:即使只是單純地針對軟件中某一個功能做完全驗證,也是幾乎不可能的。
而在早先的測試文獻中我們也不難找到類似的論述,比如在Weinberg 1986中,杰拉德?溫伯格(Gerald Weinberg)就指出我們無法測試所有的可能:“首先,人類的大腦不僅會犯錯誤,而且它的容量也是有限的。其次,沒有人的生命可以無限長。所以無論我們多想進行所有可能的測試,也無法全部考慮到;即使全部考慮到了,也無法活得足夠長來將它們進行完” [2]。而在Patton 2006中,羅恩?巴頓(Ron Patton)也強調(diào)了完全測試程序是不可能的,主要原因是“輸入量太大;輸出結(jié)果太多;軟件執(zhí)行路徑太多;軟件說明書是主觀的” [3]。在這些早先的文獻中,都如上文一樣,強調(diào)了因為操作組合與環(huán)境組合的難以窮盡給質(zhì)量證明所帶來的困難。然而從哲學(xué)的視角看,導(dǎo)致潛在場景難以窮盡的主要因素還并非海量的操作組合與環(huán)境組合,而是時空。因為從時空的角度看,即使是對一特定操作之結(jié)果的論斷也是一個全稱陳述。也就是說諸如“在設(shè)備A上運行程序,并在按鈕B的坐標(15,15)上點擊鼠標,系統(tǒng)顯示結(jié)果C”這樣一個陳述實際上就已包含了無窮多個可能的場景,因為它斷定該陳述在一切時空中都為真。但這顯然是無法證實的,因為即使我們昨天觀察到它為真、今天觀察到它為真,甚至過去數(shù)百次觀察都是如此,我們也仍不能斷言在下次觀察中必然為真。因此我們可以說,近乎無窮的操作組合與環(huán)境組合使得我們事實上無法證明軟件的完美,但真正無窮的時空因素卻是使得這類證明在理論上就已不可能的根本原因。
軟件測試并不能證明軟件是完美的,不管受測的軟件是多么簡單都是如此,當我們從時空的維度來重新審視軟件時,我們感受到了歸納的無力,在實證領(lǐng)域,不存在所謂的歸納。就像觀察到數(shù)以億計的人說漢語,也無法確保所有的人都說漢語一樣——在時空的無限論域中,一種假設(shè)哪怕是得到了再多的經(jīng)驗證據(jù)支持,其邏輯概率也永遠趨于零。所以,我們不可能通過測試來宣稱或者斷言軟件的完美。當然,這并不是說世界上不可能存在完美的軟件,而是只說——我們有可能開發(fā)出了完美的軟件而不知。
與證實全稱陳述的復(fù)雜性和不可能相比,要證偽一個全稱陳述卻是相對的簡單,因為盡管有再多的觀察也不能推論出全稱陳述為真,但反過來,只要存在一例觀察不為真我們便推倒了整個全稱陳述。這就是哲學(xué)家卡樂?波普爾(Karl Popper)所說的證實與證偽之間存在的“令人震驚的不對稱”:“單一的觀察陳述集合有時可以證偽或反駁一個全稱定律,但它不可能在確立定律的意義上證實定律。這個事實的另一個表述為:單一的觀察陳述集合可以證實一個存在陳述(這意味著證偽一個全稱定律),但無法證偽該存在陳述。這是基本的邏輯情境,它表明了一種令人震驚的不對稱” [4]。當我們證偽一個全稱陳述時,實際上是證實了“存在著某一例觀察結(jié)果與全稱陳述與推論不符”。在軟件測試中,如果我們拒絕“某軟件是完美”這一論述,那么必然是我們證實了在該軟件中至少存在著一例缺陷。我們將諸如“軟件存在缺陷”、“上帝是存在的”與“世間有黑天鵝”等稱之為存在陳述,這些陳述在形式上都是存在主義的。與全稱陳述的不可證實但可被證偽相反,存在陳述是可證實但不可被證偽的。在理論上,我們要證實這些陳述是有可能的(至少是潛在可能),但卻無法否定它們。也就是說,即使某人終此一生都未目睹過上帝,又或者我們之前所見到的一萬只天鵝全都不是黑色的,我們也不能反駁它們的存在;與之相反,哪怕是只要有一例觀察與陳述相符,這些陳述就得到了證實。因此,在關(guān)于軟件缺陷的論述中,我們可以證實某個軟件確實存在缺陷,但卻無法證偽它:即證明其“不存在”缺陷、或者認為它是完美的。事實上,我們甚至不需要進行任何的測試,也能大膽地去評價任何軟件、斷定它們“存在缺陷”,而不用擔心會遭到何種反駁——因為存在陳述是不可證偽的。
這是一個有趣的結(jié)論。如果軟件測試工作者的任務(wù)是判斷軟件是否有缺陷的話,那么可以說這份工作既是最容易的,也是最困難的。它的容易之處在于我們總是可以無任何后顧之憂地給出存在缺陷的判斷,而困難則是我們永遠也無法通過努力來證明軟件無缺陷。在現(xiàn)實世界中,人們往往會寄望測試能證明軟件的完美,或者讓測試工作者確保此時此地檢驗了的測試場景,在彼時彼地也能按預(yù)期執(zhí)行,但這卻恰恰是測試最不可能完成的任務(wù)。
測試與決定
測試工作者在測試過程中所得到的發(fā)現(xiàn)與知識,實際上既不足以證明軟件的完美,也不足以決定后續(xù)的行動。測試是一種批判、一種實驗,如果說實驗是一切知識的試金石,那么測試就是我們對軟件所宣稱的質(zhì)量的試金石。測試能告知我們的是:軟件經(jīng)受了何種廣度與強度的檢驗,以及它在什么場景下存在著缺陷,但也僅此而已。我們通過測試來獲取信息,而非給出預(yù)言。相反,測試恰恰是對來自開發(fā)人員對軟件質(zhì)量的預(yù)言(這個軟件沒問題)的檢驗與批判。正如在物理學(xué)中的分工那樣(理論物理學(xué)家進行想象、推演和猜測新的定律,但并不做實驗;而實驗物理學(xué)家則進行實驗、想象、推演和猜測),測試工作就是想盡一切辦法去尋找問題、去讓軟件失敗,除此無他,與證明扯不上半點關(guān)系。努力去維護受測系統(tǒng)、去證明我們的軟件是多少的正確,這從來就不是可取的測試精神,測試工作者永遠不會說受測軟件是無缺陷的,而只會說這軟件目前通過了眼下的檢驗,我們還沒有發(fā)現(xiàn)否定它的理由。然而這一肯定的判決也只能是暫時的,我們?nèi)詫﹄S后的否定判決開放。因此,測試可以看作是一個探索的過程、一個批判的過程,它獲取信息,而不做出決定。測試不負責決定軟件是否可以發(fā)布、也不負責決定我們是否要取消某個項目,甚至都無法決定接下來是否還需要進一步的測試。測試過程中所產(chǎn)生的信息,對于這些決定來說是重要的、也是關(guān)鍵的,但我們也必須承認,這些信息并不充分。
如果說測試永遠是不完備的,只能告知我們當下軟件經(jīng)過了何種程序的檢驗,那么,對于軟件發(fā)布后它在客戶處的表現(xiàn)、會不會出現(xiàn)問題、以及會出現(xiàn)什么問題,理論上我們都只是推斷與猜測。在這一過程中,我們通過已檢驗的場景來推斷未經(jīng)檢驗的場景,通過這一時空的檢驗結(jié)果來推斷另一時空的執(zhí)行結(jié)果。在做出是發(fā)布軟件還是繼續(xù)測試的決定時,實際上我們面臨著一種兩難,因為這里面存在著兩類可能的錯誤:第一類錯誤是軟件還有未找出的缺陷但我們過早地發(fā)布了,而另一類錯誤則是推遲發(fā)布但沒并沒有找出新的缺陷。但是遺憾的是,在這兩類可能的錯誤之間,我們別無選擇,只能選擇盡力避免其一,而不能選擇兩者都避免。因為我們越是試圖控制一類錯誤的發(fā)生概率,反過來就越是加大了犯另一類錯誤的可能性。
在統(tǒng)計概念中,這稱之為假設(shè)檢驗。在進行假設(shè)檢驗時,我們將設(shè)置一個假設(shè)為原假設(shè),用H0表示,在這里它表示
H0:軟件還存在可能找出的缺陷
第二個假設(shè)稱為備選假設(shè)或研究假設(shè),用H1表示。在決定是否應(yīng)該繼續(xù)測試時,它表示
H1:軟件不存在可能找出的缺陷
如此一來,我們設(shè)置了兩個互為矛盾的假設(shè),在真實世界里只能有其一為真。然而,就像前文中所提到的,我們有可能開發(fā)出完美的軟件而不知;因此一般來說,我們并不知道、也不能斷定哪個假設(shè)是正確的,我們只能猜測。當我們進行假設(shè)檢驗時,實際上會存在兩種可能的錯誤。第一類錯誤是當原假設(shè)正確時,我們卻拒絕了它。第二類錯誤被定義為當原假設(shè)有錯誤時,我們卻沒有拒絕。在上面的例子中,第一類錯誤就是進一步測試還能發(fā)現(xiàn)更多的問題(H0為真),但我們卻匆忙發(fā)布了軟件(拒絕了H0);而當我們?yōu)榱吮kU進行了一些冗余測試(拒絕了H1),但并沒有發(fā)現(xiàn)問題時(H1為真),第二類錯誤就發(fā)生了。我們把發(fā)生第一類錯誤的概率記為α,通常它也被稱作顯著水平;第二類錯誤發(fā)生的概率記為β。發(fā)生錯誤的概率α和β是相反的關(guān)系,這就意味著任何嘗試減少某一類錯誤的方法都會使另外一類錯誤發(fā)生的概率增加。在決定是發(fā)布還是繼續(xù)測試時,如果我們試圖減少第一類錯誤(還能進一步找到缺陷,但沒有繼續(xù)測試),我們就必須進行更多更完備地測試,但這一決定無疑就增加了犯第二類錯誤(做了更多的測試,但沒有進一步發(fā)現(xiàn)缺陷)的可能性。反過來也是如此,當我們修復(fù)一個局部缺陷而不進行完整測試的時候,實際上我們是希望避免發(fā)生第二類錯誤的可能性,但這樣做我們就肯定增加了犯第一類錯誤的可能性。
第一類錯誤(匆忙發(fā)布軟件)的成本是客戶故障成本、商譽蒙塵或法律風險等,而第二類錯誤(推遲軟件的發(fā)布)的成本則可能是冗余的測試成本、商機延誤或未能盡早地得到市場反饋。有些產(chǎn)品犯第一類錯誤的成本較高,比如航天軟件與醫(yī)療軟件,因此人們往往會進行更多更完備的測試,寧愿做了許多事后看來無謂的測試、犯第二類錯誤,也要全力避免第一類錯誤發(fā)生。另外一些軟件則相反,比如互聯(lián)網(wǎng)產(chǎn)品、或是一些市場處于生命周期早期的產(chǎn)品,在這些領(lǐng)域,商機往往是瞬息萬變、稍縱即逝的。在此若是因為犯第二類錯誤而導(dǎo)致商機延誤,與因第一類錯誤引起的軟件故障相比可能會嚴重得多。因此,綜合來看,需要做多完備的測試、何時發(fā)布軟件?這實際上是判斷哪一類錯誤更嚴重的問題,不同的行業(yè)會有不同的選擇,不同的競爭者也會有不同的選擇,而測試本身并不足以做出這一選擇。這里面涉及到的商業(yè)價值與成本評估問題,是市場工作者的領(lǐng)域,測試可以提供信息,但絕不能越俎代庖。從上述角度推論,我們可以認為在軟件領(lǐng)域里,單從測試角度是不足以決定軟件是否可以發(fā)布的,同時也不存在通用的軟件發(fā)布標準。
測試與系統(tǒng)
軟件系統(tǒng)無疑是復(fù)雜的,作為一個復(fù)雜系統(tǒng),我們無法證明其正確性。這一點與自然界規(guī)律是相似的,也正因為自然界的復(fù)雜性與不可知,對于自然科學(xué)的規(guī)律,我們只能證偽不能證實。然而,盡管同是因為復(fù)雜性導(dǎo)致了軟件行為與自然界行為的不可證實,但這兩類系統(tǒng)的復(fù)雜性來源與復(fù)雜程度卻是不同的,認識到這點將有助于我們進一步理解軟件測試。經(jīng)濟學(xué)家肯尼思?博爾?。↘enneth Boulding)根據(jù)復(fù)雜程度,將系統(tǒng)分為九大類型[5]:
| 系統(tǒng) | 說明 |
|---|---|
| 靜態(tài)架構(gòu) | 擁有靜態(tài)結(jié)構(gòu),如晶體里原子的排列或動物的骨骼。 |
| 時鐘結(jié)構(gòu) | 具有預(yù)定運動模式的簡單動態(tài)系統(tǒng),如鐘表和太陽系。 |
| 自調(diào)系統(tǒng) | 具有根據(jù)某些外給目標或準則自我調(diào)節(jié)的能力,如恒溫器。 |
| 開放系統(tǒng) | 能夠通過從環(huán)境獲取資源進行自我維系,如生物細胞。 |
| 衍生系統(tǒng) | 不是通過復(fù)制,而是通過含有成長指令的種子或卵進行繁衍,例如雞和蛋系統(tǒng)。 |
| 內(nèi)像系統(tǒng) | 具有獲取信息詳盡地認識環(huán)境的能力,以及把這些認識組織成關(guān)于環(huán)境整體形象或知識結(jié)構(gòu)的能力,這是動物能力所能達到的層次。 |
| 抽象系統(tǒng) | 具有自我意識,能夠運用語言,人類行為處于這一層次。 |
| 社會系統(tǒng) | 由具有第7層次能力并遵循共同的社會秩序與文化的行動者組成。 |
| 絕對不可知的系統(tǒng) | 宇宙。 |
博爾丁的分類給我們許多啟發(fā)。首先,它展示了世界上系統(tǒng)存在的廣泛性與多樣性。其次,我們在上述分類中可以看出軟件系統(tǒng)與自然系統(tǒng)之間的不同:它們分別屬于第3層與最為復(fù)雜的第9層。
作為第9層的自然系統(tǒng),其復(fù)雜性在于,我們對于它內(nèi)部機制與目標完全不可知。無論科學(xué)如何發(fā)展,科學(xué)都不是一個確定的或即成的陳述系統(tǒng),也不是一個朝著一個終級狀態(tài)穩(wěn)定前進的系統(tǒng),而僅僅只是關(guān)于真實世界的猜想。已故的諾貝爾物理學(xué)獎得主理查德?費恩曼(Richard Feynman)有一個關(guān)于自然系統(tǒng)的生動比喻:”可以作一想象:組成這個世界的運動物體的復(fù)雜排列似乎有點像是天神們所下的一盤偉大的國際象棋,我們則是這盤棋的觀眾。我們不知道弈棋的規(guī)則,所有能做的事就是觀看這場棋賽。當然,假如我們觀看了足夠長的時間,總歸能看出幾條規(guī)則來……除了我們還不知道所有的規(guī)則外,我們真正能用已知規(guī)則來解釋的事情也是非常有限,因為幾乎所有的情況都是極其復(fù)雜的,我們不能領(lǐng)會這盤棋中應(yīng)用這些規(guī)則的走法,更無法預(yù)言下一步將要怎樣?!?[6]大自然的復(fù)雜性與內(nèi)在機制的不可知使得我們一切有關(guān)這個世界的知識都只是猜測,而證實與證偽之間驚人的不對稱又使得我們即使找到了真理的時候也無法知道這一點。因此對于自然科學(xué),波普爾說:“我們不知道,我們只能猜測”。
與自然系統(tǒng)相比,軟件系統(tǒng)有著與之截然不同的復(fù)雜性來源。作為人類科技與意志的產(chǎn)物,我們并非對其內(nèi)部機制與目的不可知,相反,是我們設(shè)計了它的內(nèi)部機制與目標,可以說,我們是它的造物主。但我們?nèi)俗鳛樵煳镏?,與自然系統(tǒng)萬能的造物主相比,又是極為渺小和有限的。在構(gòu)造一個有大量執(zhí)行路徑的軟件系統(tǒng)的過程中,我們難免會想當然、會忘記、會迷糊、也會疏忽,甚至還有可能存在道德風險,這些都使得我們不應(yīng)當將軟件視為一個必然會遵從設(shè)計與預(yù)期的系統(tǒng),它是復(fù)雜的、不確定的,因而也是需要測試與批判的。一方面,是人無與倫比的智慧賦予了它遵從預(yù)期行動的能力,另一方面,也是人不可避免的局限性為其埋下了可能偏出預(yù)期的種子。也就是說,其行為的不可預(yù)知不在于機制的不可知,而是我們并不能保證我們能完全不出錯地將設(shè)計的機制轉(zhuǎn)換為軟件所預(yù)期的行為。正是因為這種結(jié)果的不可保證性,我們才需要測試——我們設(shè)計了內(nèi)部機制但無人能保證它的實現(xiàn)。
這就是軟件系統(tǒng)的兩面性,一方面,我們不能因為知其既定的機制,就確信它一定能按照這些機制來實現(xiàn)其行為,因而我們需要懷疑、需要忘掉這些機制去測試它的各種輸入與輸出,黑盒測試在任何時候都是必不可少;另一方面,我們又不能將其看作更高復(fù)雜層次的系統(tǒng),假裝我們對其內(nèi)部機制一無所知。如此一來,我們實際上是完全放棄了原本可以從內(nèi)部理解其行為的便利,而這樣的測試設(shè)計必然是欠缺效率的,因而從白盒的角度分析軟件系統(tǒng)也是同樣必不可少。
今天,關(guān)于軟件的復(fù)用,我們已經(jīng)取得了長足的發(fā)展。從函數(shù)級復(fù)用到組件級復(fù)用、再到框架級復(fù)用,軟件復(fù)用一方面縮短了軟件系統(tǒng)的開發(fā)周期,另一方面也減少了缺陷的引入。顯然,如果我們不關(guān)心軟件的內(nèi)部機制的話,那么測試人員將不能從軟件復(fù)用中得到任何好處:因為站在黑盒的角度,我們不能對軟件的實現(xiàn)作任何假設(shè),理論上我們必須測試一切窗口控件,即使這些窗口控件是復(fù)用的,早已經(jīng)過了千錘百煉;我們也必須測試一切底層API,即使這些底層API都是標準的系統(tǒng)調(diào)用。但是,如果我們能正視這些機制、利用這些機制的話,我們的測試將更有針對性,也更有效率。我們不應(yīng)該認為測試應(yīng)該從零開始,就像我們不應(yīng)該認為一切科學(xué)理論都應(yīng)該從零開始一樣。在科學(xué)的各個領(lǐng)域里,往往都只存在著少數(shù)的一些基本的公理,在這些少數(shù)公理的基礎(chǔ)上,科學(xué)家們借助大量的次級理論構(gòu)建出了巍巍的科學(xué)大廈。因此,絕大多數(shù)的軟件系統(tǒng)與科學(xué)理論一樣,實際上都不是孤立地存在的,它們都包含了一些暫時經(jīng)受了批判的部分。當我們檢驗系統(tǒng)時可以選擇將這一部分看作是背景知識,暫時地接受,而將精力更多地集中在檢驗?zāi)切└嗳?、更容易發(fā)現(xiàn)缺陷的部分。
小結(jié)
在經(jīng)典著作《人類理解研究》中,哲學(xué)家大衛(wèi)?休謨曾如此設(shè)問并回答:“我以前所食的那個面包誠然滋養(yǎng)了我,那就是說,具有那些可感性質(zhì)的那個物體在那時候,是賦有那些神秘的能力的。但是我們果能由此推斷說,別的面包在別的時候也一樣可以滋養(yǎng)我、而且相似的可感性質(zhì)總有相似的神秘能力伴隨著它么?這個結(jié)論在各方面看來都不是必然的。”[7]休謨因而指出了歸納的局限性,即我們不能因為曾經(jīng)看到那樣一個物象總有那樣一個結(jié)果伴隨著它,就可以預(yù)測相似的物象也會有相似的結(jié)果。在大自然中,這種不可預(yù)測性是因為自然系統(tǒng)的不可知。而對于軟件測試,盡管我們知道不少,但我們也不能因觀察到軟件系統(tǒng)的某一功能在此時此地沒問題,因而斷定在客戶處也必然是沒問題的。在要求對事物做出準確預(yù)測的時候,只要我們沒有把握到事物的必然性,那么不管是對事物內(nèi)部有一知半解還是完全一無所知,實際上是沒有區(qū)別的。因此,在探討關(guān)于測試我們知道什么這一問題時,我們大可將軟件視為實現(xiàn)上不可知的系統(tǒng),這是測試的本質(zhì),也是測試工作的起點。然而,當我們探討如何去測試與批判一個系統(tǒng)時,則將軟件系統(tǒng)視為在一定程度上是可知的又是非常有必要的,因為這是關(guān)于效率的問題。因此,我們?nèi)绾慰创軠y系統(tǒng),取決于我們需要回答或解決的是哪類問題。但無論如何,軟件測試都是批判而非證明:從不可知的觀點出發(fā),我們不能證明;而從有限可知的觀點看,又有太多的東西可以不去證明。忙亂地寫著冗余的測試用例的測試人員,就像是在路燈下尋找車鑰匙的人一樣,我們不能說他錯了,但他肯定不可能是卓有成效的。優(yōu)秀的測試工作者是將測試看作是批判、是藝術(shù)的人,他們勇敢地承認自己的局限性,卸下了證明的重負,站在巨人的肩上,然后看得更遠。
注釋
[1]: Karl Popper. 《Realism And The AIM of Science》. 1983.
[2]: Gerald M. Weinberg. 《Perfect Software: And Other Illusions about Testing》. 1986.
[3]: Ron Patton. 《Software Testing》. 2006.
[4]: Karl Popper. 《Realism And The AIM of Science》. 1983.
[5]: Kenneth E. Boulding. 《General System Theory: The Skeleton of Science》. Management Science, 2. 1956.
[6]: Richard Feynman. 《The Feynman Lectures On Physics, Volume I》. 1963.
[7]: David Hume. 《An Enquiry Concerning Human Understanding》. 1927.
來源
《51測試天地》第35期,2014年,by 殷亮