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