修改代码的艺术----- 2.2 高层测试 2.3 测试覆盖
2.2? 高層測(cè)試
單元測(cè)試的確很棒,但高層測(cè)試也有其一席之地。所謂高層測(cè)試便是那些覆蓋了某個(gè)應(yīng)用中的場(chǎng)景和交互的測(cè)試。高層測(cè)試可以用來(lái)一下子就確定一組類(lèi)的行為。能夠這樣做往往就意味著你可以更容易地為單個(gè)類(lèi)編寫(xiě)測(cè)試。
2.3? 測(cè)試覆蓋
那么在一個(gè)遺留項(xiàng)目中我們究竟該如何著手進(jìn)行修改 呢?我們首先注意到,如果可以選擇的話,在進(jìn)行修改時(shí)有測(cè)試“罩著”總是要安全一些的。對(duì)代碼的修改可能會(huì)引入bug,因?yàn)槲覀儺吘故侨硕皇巧瘛5偃?在修改代碼之前先用測(cè)試將代碼“護(hù)住”,我們就能更容易地捕獲到在改動(dòng)過(guò)程中所犯的錯(cuò)誤了。
圖2-1展示了一小組類(lèi)。我們想要改動(dòng)InvoiceUpdateResponder的getResponseText方法以及Invoice的getValue方法。這兩個(gè)方法是我們的改動(dòng)點(diǎn),我們可以通過(guò)為它們所在的類(lèi)編寫(xiě)測(cè)試來(lái)覆蓋它們。
圖2-1? 發(fā)票更新類(lèi)
要想編寫(xiě)并運(yùn)行測(cè)試,我們先要能夠在一個(gè)測(cè)試用具中 創(chuàng)建InvoiceUpdateResponder和Invoice的實(shí)例。那么我們能否做到這一點(diǎn)呢?看起來(lái)創(chuàng)建Invoice的實(shí)例可以不費(fèi)吹灰之力 地完成,因?yàn)樗幸粋€(gè)無(wú)參的構(gòu)造函數(shù)。而InvoiceUpdateResponder的情況則可能要復(fù)雜一些,它的構(gòu)造函數(shù)接受一個(gè) DBConnection,參數(shù)這是一個(gè)數(shù)據(jù)庫(kù)連接,必須真正連接到一個(gè)實(shí)實(shí)在在的數(shù)據(jù)庫(kù)。問(wèn)題來(lái)了,在測(cè)試中我們?cè)撛鯓犹幚磉@種情況呢?我們是否要為此 建立一個(gè)數(shù)據(jù)庫(kù),并在其中存上測(cè)試所需的數(shù)據(jù)呢?這么做的工作量可不小。而且有沒(méi)有考慮到涉及數(shù)據(jù)庫(kù)的測(cè)試會(huì)比較慢呢?無(wú)論如何,我們?cè)谧鲞@個(gè)測(cè)試的時(shí)候 對(duì)數(shù)據(jù)庫(kù)并不特別關(guān)心,只是想覆蓋我們對(duì)InvoiceUpdateResponder和Invoice的改動(dòng)。此外還有一個(gè)更大的問(wèn)題,即 InvoiceUp- dateResponder的構(gòu)造函數(shù)需要一個(gè)InvoiceUpdateServlet作為參數(shù)。創(chuàng)建一個(gè)這樣的對(duì)象可想而知有多麻煩。當(dāng)然我們可以改 動(dòng)一下InvoiceUpdateResponder的代碼,讓它不再接受InvoiceUpdateServlet為參數(shù)。如果 InvoiceUpdateResponder只是需要從InvoiceUp- dateServlet那里獲取很少一點(diǎn)信息的話,我們就可以將這些信息傳給它,而不是傳給它整個(gè)servlet,然而,在做上述改動(dòng)的時(shí)候我們是不是也 需要做個(gè)測(cè)試來(lái)確保改動(dòng)的正確性呢?
以上這些問(wèn)題都屬于依賴(lài)問(wèn)題。當(dāng)一個(gè)類(lèi)直接依賴(lài)于某些難以在測(cè)試中使用的東西時(shí),這個(gè)類(lèi)就是難以修改和處理的。
依賴(lài)性是軟件開(kāi)發(fā)中最為關(guān)鍵的問(wèn)題之一。在處理遺留代碼的過(guò)程中很大一部分工作都是圍繞著“解除依賴(lài)性以便使改動(dòng)變得更容易”這個(gè)目標(biāo)來(lái)進(jìn)行的。
那么,我們具體又該怎么做呢?在不改變代碼的前提下 我們?nèi)绾尾拍軐y(cè)試安置到位呢?不幸的是,在許多情況下想要做到這一點(diǎn)是不大容易的,某些時(shí)候甚至根本不可能。就在我們剛剛看到的這個(gè)例子中,我們當(dāng)然可 以通過(guò)使用一個(gè)真實(shí)的數(shù)據(jù)庫(kù)來(lái)解決DBConnection問(wèn)題,然而servlet問(wèn)題又該怎么辦呢?我們是不是也要?jiǎng)?chuàng)建一個(gè)完整的servlet對(duì)象 并將它傳遞給InvoiceUpdateResponder的構(gòu)造函數(shù)呢?我們能否將這個(gè)servlet設(shè)置到正確的狀態(tài)呢?可能吧。但假如我們將要測(cè)試 的是一個(gè)圖形用戶(hù)界面的桌面應(yīng)用程序又該怎么辦呢?這樣一個(gè)應(yīng)用程序也許并沒(méi)有任何可供我們測(cè)試時(shí)利用的可編程接口,其邏輯可能被捆綁在GUI類(lèi)當(dāng)中。這 時(shí)候我們?cè)撛趺崔k呢?
遺留代碼的困境
我們?cè)谛薷拇a時(shí),應(yīng)當(dāng)有測(cè)試在周?chē)白o(hù)”著。而為了將這些測(cè)試安置妥當(dāng),我們往往又得先去修改代碼。
在上面的Invoice例子當(dāng)中,我們可以試著在一 個(gè)更高的層別來(lái)進(jìn)行測(cè)試。如果對(duì)于某個(gè)特定的類(lèi)來(lái)說(shuō),不改變它就難以為它編寫(xiě)測(cè)試的話,那么轉(zhuǎn)而去測(cè)試使用它的那些類(lèi)往往會(huì)簡(jiǎn)單一些。然而不管怎么樣,我 們通常最終還是免不了要在某個(gè)點(diǎn)上解開(kāi)類(lèi)之間的依賴(lài)。在當(dāng)前的這個(gè)例子中,我們可以解開(kāi)InvoiceUpdateResponder對(duì) InvoiceUpdateServlet的依賴(lài):只需將InvoiceUpdateResponder真正需要的東西傳給它就行。 InvoiceUpdateResponder需要的是InvoiceUpdateServlet所持有的一組發(fā)票ID。同樣,我們也可以解開(kāi) InvoiceUpdate- Responder對(duì)于DBConnection的依賴(lài):只需引入一個(gè)接口(IDBConnection)并將Invoice- UpdateResponder改為使用該接口即可。圖2-2展示了這些類(lèi)在上述改動(dòng)之后的樣子和關(guān)系。
那么,在沒(méi)有測(cè)試保護(hù)的情況下進(jìn)行上述的重構(gòu)到底安 不安全呢?實(shí)際上它們可以是安全的。上述的用于解開(kāi)InvoiceUpdateResponder對(duì)InvoiceUpdateServlet和對(duì) DBConnection的依賴(lài)的兩種重構(gòu)手法分別稱(chēng)作樸素化參數(shù)(Primitivize Parameter,302頁(yè))和接口提取(Extract Interface,285頁(yè))。在本書(shū)的最后,解依賴(lài)的技術(shù)一部分中對(duì)它們有詳細(xì)描述。在解依賴(lài)時(shí),我們通??梢圆捎镁帉?xiě)測(cè)試的手段來(lái)讓較具侵入性的修 改更為安全。訣竅就在于要非常保守地進(jìn)行上述最初的重構(gòu)。
圖2-2 ?解除依賴(lài)后的發(fā)票更新類(lèi)
當(dāng)我們的改動(dòng)可能會(huì)引入錯(cuò)誤的時(shí)候, 保守地進(jìn)行改動(dòng)就成了不二之選,然而有時(shí)候(為了讓測(cè)試覆蓋代碼而解依賴(lài))結(jié)果代碼卻并不像前面的例子中那樣光鮮漂亮,例如我們可能只是為了能夠?qū)y(cè)試安 置到位而為某個(gè)方法引入了某個(gè)在產(chǎn)品代碼中并不嚴(yán)格需要的形參,或者以古怪的方式將某個(gè)類(lèi)分裂開(kāi)了。當(dāng)這么做的時(shí)候,我們可能最終會(huì)令代碼看上去稍微糟糕 一些。另一方面,如果不那么保守,則我們可以立即解決這個(gè)問(wèn)題。但話雖如此,具體還要看這么做會(huì)帶來(lái)多大的風(fēng)險(xiǎn)。如果錯(cuò)誤是(它們的確通常是)個(gè)重要的考 慮因素的話,保守改動(dòng)常常是有好處的。
當(dāng) 在遺留代碼中解依賴(lài)時(shí),你常常不得不暫時(shí)將自己的審美感放在一旁。有些依賴(lài)能夠干凈利落地解除,而有些從設(shè)計(jì)的角度來(lái)看最終還是解決得不那么完滿(mǎn)。這就好 像做手術(shù)總要有一個(gè)刀口一樣,刀口在縫合之后可能會(huì)變成一道疤痕,你的改動(dòng)也可能會(huì)在代碼中留下“疤痕”,然而“疤痕”之下的東西則已得到了治愈。
而且,如果以后能夠用測(cè)試覆蓋“疤痕”四周(即你當(dāng)初解依賴(lài)的點(diǎn))的話,你就可以將“疤痕”也抹掉了。
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專(zhuān)家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的修改代码的艺术----- 2.2 高层测试 2.3 测试覆盖的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 蓝屏代码含义大全
- 下一篇: SICStus Prolog 3.10.