編寫可測試的程序 軟件測試
今天同事在autobuild腳本中加入了autotest,我們項目中已經有了不少自動測試程序,但分布比較零散,沒有一個地方把它們集中起來,現在這些測試程序終于有了歸宿。雖然這只是測試程序的調用入口點,并不完成真正的測試功能。但它標志著一個好的開始,希望借此契機,我們能把自動測試規范起來。
說到自動測試,不少人都會想到單元測試框架(如cppunit/junit),或者gui測試工具(winrunner)。我想這是一種誤解,gui測試工具效果不佳是眾所周知的,只有確實無法分離界面和實現,或者作為輔助手段時,才有必要存在。至于單元測試框架,這幾年來它與自動測試如影相隨,但從客觀上講,它也只是一個簡化測試程序開發的輔助手段。
自動測試與其說是測試的范疇,還不如說是設計的范疇。能不能自動測試,完全是由設計決定的,單元測試框架和gui測試工具的作用微乎其微。為設計良好的模塊編寫自動測試程序非常簡單,要不要單元測試框架完全是個人偏好。我以前開發過一個單元測試框架,但現在幾乎很少用單元測試框架去編寫測試程序。
如何編寫可測試的程序呢?
1. 分離界面和實現。這是老生常談的話題了,但說起來容易做起來難。我見過不少把MVC概念玩得很熟的人,真正寫出來的代碼卻不像那回事兒。要真正精通MVC,還要多讀多練,多思考,而不是拿著個概念在那里念念有詞。
2.契約式設計。契約式設計是一種非常好的思想,《Design By Contract 原則與實踐》是一本好書(孟巖老師翻譯的,翻譯質量很高),它封面上的六大原則,每條原則的價值都遠遠大于該書的價錢。在C/C++中直接實現契約式設計有些困難,不過這些思想為自動測試大開方便之門,其中有五條原則都可以應用于自動測試:1.區分命令與查詢。2.將基本查詢與派生查詢分開。3.針對每一個派生查詢,設定一個后驗條件,用一個或多個基本查詢的結果來定義它。4.對于每個命令都寫一個后驗條件,規定每個基本查詢的值。6.撰寫不變式來定義對象的恒定屬性。
3.注意模塊的粒度。有的人確實把界面和內部邏輯分開了,但是內部邏輯成了一個大泥團,它的邏輯過于復雜,要想為它編寫一個稍為完整的測試程序,那是相當困難的事,即使寫出來了,以后維護也成問題。設計的主要過程就是責任的劃分,把每個類的責任劃分清楚了,系統的對象模型也就出來了,這也是最考究設計者功力的地方。盡管我們有一個萬能良方作為指導:每個類承擔單一的責任。但在真正設計時,可不像那么簡單。
4.減少模塊之間的耦合。我見過幾個大系統,里面的模塊耦合非常緊密,甚至從源代碼重新build整個系統都不可能。為了編譯它,要先用一些空函數生成一個lib文件,以便讓一部分模塊先鏈接過去。這些系統沒有GUI界面,不用費心去分離界面和實現,但是自動測試仍然很困難,原因是模塊之間耦合太緊了,運行一個測試程序就相當于運行整個系統。減少不必要的耦合并不難,分層設計就是最好的方法之一,各層之間以標準的接口進行交互,上層可以直接調用下層的服務,而下層只能通過消息或者回調函數與上層通信。另外,像訪Visitor/Builder等設計模式也能很好的解開模塊之間的耦合。其實,歸根結底就一句話:針對接口編程。
5.控制隨機因素。自動測試并不是簡單的把測試程序運行一遍,它要檢查運行結果是否與期望相符。如果你根據不知期望的結果是什么,還談什么自動測試呢?然而系統中往往有些隨機因素,這些隨機因素不是指內存越界或者未初始化的變量,而是指一些有意加入隨機因素,比如在游戲中使用的隨機數,它們會影響我們對測試的結果的判斷。這時我們可以包裝一下系統的隨機數函數,讓我們可以得到上一次的隨機數,以便對期望結果進行估計。另外像網絡信號、線程間通信和并發也存一定的隨機性,要盡量減少它們對測試結果的影響。
6.為支持測試提供額外的函數。大多數情況下,一個模塊只會實現它必要的功能,這是應該的,保證接口最小化。但這也為我們檢查期望值造成了障礙,有時我們無法檢查一個操作的具體影響,不要再猶豫了,為它增加一些查詢函數吧,這些工作量會由于減少調試時間而得到回報。
對于自動測試,不要被XP和單元測試框架這類概念搞迷惑了,簡單一點,先做起來,再完善它,否則它永遠都只是個美好的愿望。自動測試也不是靈丹妙藥,不要對它期望過高,它不能解決所有問題,但它確實很有價值。
文章來源于領測軟件測試網 http://www.kjueaiud.com/