1. 程式人生 > >基於測試概念進行程式碼設計的七條基本原則

基於測試概念進行程式碼設計的七條基本原則

當設計大型程式的時候,您必須時刻留心不同設計選項對諸如效能和可擴充套件性這樣的特徵的影響。隨著軟體產品的日漸複雜及其無所不在的部署,軟體的“可測試性”也成了更重要的考慮事項。

徹底測試程式碼的重要性是顯然的。花在編寫測試和測試程式碼上的時間和精力給您帶來的回報是維護成本的大幅降低。

然而,除非您很小心,否則您花在測試程式碼上的精力可能會首先達到花在編寫程式碼上的精力的幾倍!我曾看到程式設計師們齊心協力地對他們的全部程式碼進行單元測試,結果花在上面的時間使大多數人都以沮喪而告終。

幸運的是,沒有必要這樣。在您設計軟體的時候應用一些基本原則,編寫易於測試、甚至使測試成為樂趣的程式碼是可能的。

跟其它編碼原則一樣,這些原則也不是不容置疑或不可改變的教條。有時候打破這些規則也是必要的。因此,理解每條原則背後的動機和判斷何時這些動機不適用(或應讓位給更關心的問題)的能力是很重要的。

原則1:到GUI檢視的外面去

儘可能把程式碼移到GUI檢視的外面。然後各種GUI動作就能成了模型上的簡單方法呼叫。為什麼您需要這樣做呢?

對GUI測試者來說,通過方法呼叫測試功能比間接地測試功能容易的多。

另一個好處是它使修改程式功能而不影響檢視變的更容易。

當然,檢視中也可能存在錯誤。在理想情況下,對程式的測試將同時檢查模型和檢視。

原則2:使用型別進行錯誤檢查

型別是您的朋友 — 儘可能多地用型別系統自動檢查錯誤。

型別能在程式執行之前自動捕捉程式中的錯誤。沒有靜態型別檢查的話,型別錯誤將作為破壞者逗留在您的程式中,直到恰當的執行路徑碰巧把它揭露出來為止。

最大限度地發揮使用型別的長處是棘手的。通常,一組資料結構可以在一個抽象級別上一起使用,或者被分出,成為一個單一的、更高抽象級別的一個新的相關資料型別。

事實上,程式語言自身的歷史可以看成是可以程式設計的抽象級別的逐漸提高。組合語言提供了位元到整數和浮點數的抽象。接下來是記錄和函式抽象,然後又是諸如物件、類、執行緒以及異常這樣的抽象。

在每一抽象級別上,達到與更高級別抽象一致的功能是可能的,但那實質上僅僅是耗費更多精力,冒更多的錯誤風險。

在面嚮物件語言(其它現代語言也一樣)中,一個程式設計師在設計抽象上有很大的靈活性。在哪個抽象級別上設計程式就成了基於折衷的決定,比如由抽象級別提供的更多的健壯性和由於不能在更低抽象級別上工作而帶來的表達性(有時是效能)的損失。

通常,高級別抽象帶來的健壯性和簡單性的價值很少被其它考慮事項超過。

原則3:使用調節器避免“故障線路”(fault line)

我用“故障線路”來指獨立元件之間的介面,獨立元件之間和元件與其相應子元件之間相比,很少有互動。這種故障線路的一個典型示例是 GUI 檢視和它的模型之間的介面。其它示例包括在編譯器中處理的不同階段之間的介面或作業系統的核心和使用者介面之間的介面。

找出程式的故障線路,然後用具有轉發功能的調節器快速訪問聚合元件。

沿著故障線路隔離測試每個元件通常更容易。但如果每個元件暴露的物件有很多,或者元件中您想測試的一些物件只有通過多個巢狀引用才能訪問,那麼測試就會變的很乏味。

不用隔離測試,而是擁有您在它上面呼叫您想測試的各種方法的單個調節器物件通常是有幫助的。這個物件然後能把這些方法呼叫轉發到適當的地方。

沿著相同線路,設計和自己的測試程式碼串聯在一起的程式元件介面是有益的。這將使您把注意力集中在使這些介面儘可能簡單上。

原則4:方法:小型簽名和預設引數

使用小型方法說明和過載帶預設方法引數的方法將使您在測試中呼叫這些方法變的愉快的多。否則,在測試這些方法時您將不得不構造額外引數。如果引數很大,那麼將很快導致程式碼膨脹。更糟的是,它會誘使您編寫比在其它情況下更少的測試。

原則5:訪問器不應修改記憶體狀態

請在您的測試中使用不修改記憶體狀態的訪問器來檢查物件狀態。

在某些方面,測試和實驗室試驗相似。它們都想證明特定假設有效。如果特定檢查動作改變了該領域的狀態,那麼要這樣做會變得困難的多。

與量子力學領域不同,計算機程序的狀態可以不修改就被檢查。使用這種原則對您有好處。

原則6:用介面說明外部程式元件

用介面說明外部程式元件使得我們可以容易地在測試案例中模擬這些元件。

這條原則能節省大量時間,特別是當外部元件的實現還未完成時。通常,大多數基本元件都不能準時可用。如果這些元件不在適當位置您就不能測試您自己的程式碼的話,那麼您就在朝災難走去。您的客戶不會關心您只有兩個小時來整合遲到了兩週的元件。他們知道的全部就是整套產品被延期了和這是違約的。

原則7:優先編寫測試程式碼

優先編寫測試程式碼。這是標準的XP方法,但卻總有一種忽視它的誘惑。

每次我屈服於這種誘惑時,我都感到後悔。假設您正努力生產正確的程式碼,那麼您好象能從推遲編寫測試程式碼中節省的時間其實只是一個幻想。

注意:這不是說您應該一次性編寫全部測試程式碼後,再一次性全部實現。編寫一些測試程式碼,實現它們,再編寫一些測試程式碼,再實現它們等等是個更好的辦法。設計以這種方式得以進展;在實現階段捕捉錯誤並在下一組測試中改正它。以這種方式編寫測試也更少會使人畏縮。

程式碼比您需要的還多?

只需一點點努力,就可能容易地對任何程式進行徹底的測試。當然,不可避免存在這些原則不適用的情況;於是,看起來好像不可能對功能進行測試。

當出現這些情況時,我盡力退一步地看這個問題,“我怎樣才可能測試這種程式碼?”相反地,我問自己,“我怎樣才能以可測試方式編寫這些程式碼呢?”這種想法上的改變的結果經常是增加了大量 僅僅服務於簡化測試的功能。

什麼?別擔心!出現這種情況完全正常。

就象很多現有的設計模式,它們只是為了增加程式的可擴充套件性就往程式中新增很多類(例如 visitor、decorator 等等),開發簡化測試的新模式是可以接受的。實際上,面嚮物件語言的很多特徵都是為了簡化擴充套件而包含進去的;為什麼語言的未來版本(或全新的語言)不應包含簡化測試的特徵。

對 Java 語言來說,這已經開始。人們計劃在未來版本中包含很多更強大的型別系統、斷言(assertion)等等。就象面向物件的語言已經增加了我們重用和擴充套件現有程式碼的程度,將來,面向測試的設計和特徵將幫助我們增強新老程式碼的健壯性。