本文描述了三種單元測試的組織方法:自上而下法,自下而上法和分離法。組織方法是制定
單元測試策略和擬制測試計劃的關鍵因素;選擇不適當的方法會對單元測試成本和軟件維護
開支造成不利影響。這里將推薦一種基于分離法的單元測試策略。
一、介紹
單元測試是對軟件單個組件(單元)進行的測試。盡管它的編碼是不同類型的,而且有兩個
不同的階段,但單元測試通常被認為是一種組合代碼以及軟件生命周期單元測試階段的一部
分。在Ada,C 和C++程序中最基本的設計和代碼單元是單個的子程序(如過程,函數,成員
函數)。Ada 和C++提供將基本的單元群組成包(這里指Ada 語言)和復合類(C++語言)的
功能。而針對Ada 和C++程序的單元測試就是要測試語境中所包含的包和類。當設計一個單
元測試的策略時,可以采用三種基本的組織方法。它們分別是自上而下法、自下而上法和分
離法。在接下來的第二、第三和第四部分將對上述三種方法的詳細內容、各自的優點和缺點
分別進行介紹。在文章中要一直用到測試驅動和樁模塊這兩個概念。所謂的測試驅動是指能
使軟件執行的軟件,它的目的就是為了測試軟件,提供一個能設置輸入參數的框架,并執行
這個框架單元以得到相應的輸出參數。而樁模塊是指一個模擬單元,用這個模擬單元來替代
真實的單元完成測試。
二、自上而下的測試
1. 詳述
在自上而下的測試過程中,每個單元是通過使用它們來進行測試的,這個過程是由調用這些
被測單元的其他獨立的單元完成的。首先測試最高層的單元,將所有的調用單元用樁模塊替
換。接著用實際的調用單元替換樁模塊,而繼續將較低層次的單元用樁模塊替換。重復這個
過程直到測試了最底層的單元。自上而下測試法需要測試樁,而不需要測試驅動。圖2.1 描
述了使用測試樁和一些已測試單元來測試單元D 的過程,假設單元A,B,C 已經用自上而下
法進行了測試。由圖2.1 得到的是一個使用基于自上而下組織方法的單元測試計劃,其過程
可以描述如下:
步驟1:測試A 單元,使用B,C,D 單元的樁模塊。
步驟2:測試B 單元,通過已測試過的A 單元來調用它,并且使用C,D 單元的樁模塊。
步驟3:測試C 單元,通過已測試過的A 單元來調用它,并且使用已通過測試的B 單元和D 單
元的樁模塊。
步驟4:測試D 單元,從已測試過的A 單元調用它,使用已測試過的B 和C單元,并且將E,F
和G 單元用樁模塊代替。(如圖2.1 所示)
步驟5:測試E 單元,通過已測試過的D 單元調用它,而D 單元是由已通過測試的A 單元來調
用的,使用已通過測試的B 和C 單元,并且將F,G,H,I和J 單元用樁模塊代替。
步驟6:測試F 單元,通過已測試過的D 單元調用它,而D 單元是由已通過測試的A 單元來調
用的,使用已通過測試的B,C 和E 單元,并且將G,H,I和J 單元用樁模塊代替。
步驟7:測試G 單元,通過已測試過的D 單元調用它,而D 單元是由已通過測試的A 單元來
調用的,使用已通過測試的B,C 和F 單元,并且將H,I 和J 單元用樁模塊代替。
步驟8:測試H 單元,通過已測試過的E 單元調用它,而E 單元是由已通過測試的D 單元來調
用的,而D 單元是由已通過測試的A 單元來調用的,使用已通過測試的B,C,E,F,G 和H
單元,并且將J 單元用樁模塊代替。
步驟9:測試J 單元,通過已測試過的E 單元調用它,而E 單元是由已通過測試的D 單元來調
用的,而D 單元是由已通過測試的A 單元來調用的,使用已通過測試的B,C,E,F,G,H
和I 單元。
圖2.1 自上而下法
2. 優點
自上而下單元測試法提供了一種軟件集成階段之前的較早的單元集成方法。實際上,自上而
下單元測試法確實將單元測試和軟件集成策略進行了組合。單元的詳細設計是自上而下的,
自上而下的測試實現過程使得被測單元按照原設計的順序進行,因為單元測試的詳細設計與
軟件生命周期代碼設計階段的重疊,所以開發時間將被縮短。在通常的結構化設計中,高等
級的單元提供高層的功能,而低等級的單元實現細節,自上而下的單元測試將提供一種早期
的“可見”的功能化集成。它給予單元測試一種必要的合理的實現途徑。較低層次的多余功能
可以通過自上而下法來鑒別,這是因為沒有路徑來測試它。(但是,這可能在區分多余的功
能和沒有被測試的功能時帶來困難)。
3. 缺點
自上而下法是通過樁模塊來進行控制的,而且測試用例常常涉及很多的樁模塊。對于每個已
測單元來說,測試變得越來越復雜,結果是開發和維護的費用也越來越昂貴。依層次進行的
自上而下的測試,要達到一個好的覆蓋結構也很困難,而這對于一個較為完善、安全的關鍵
性應用來說至為重要,同時這也是很多的標準所要求的。難于達到一個好的覆蓋結構也可能
導致最終的多余功能和未測試功能之間的混亂。由此,測試一些低層次的功能,特別是錯誤
處理代碼,將徹底不切實際。
一個單元的變化往往會影響對其兄弟單元和下層單元的測試。例如,考慮一下D 單元一個變
化。很明顯,對D 單元的單元測試不得不發生變化和重新進行。另外,要使用已測試單元D
的E、F、G、H、I 和J 單元也不得不重新測試。作為單元D 改變的結果,上述測試自身可能
也不得不發生改變,即使單元E、F、G、H、I 和J 實際上并沒有改變。這將導致當變化發生
時,重復測試帶來的高成本,以及高額的維護成本和高額的整個軟件生產周期的成本。
在為自上而下測試法設計測試用例當中,當被測單元調用其他單元時需要測試人員具備結構
化知識。被測試單元的順序受限于單元的層次結構,低層次的單元必須要等到高層次的單元
被測試后才能被測試,這樣就形成了一個“又長又瘦”的單元測試階段。(然而,這可能會導
致測試詳細設計與軟件生命周期編碼階段的整體重疊。)如圖2.1 所示的例子程序中各個單
元之間的層次關系十分簡單,在實際的編程過程中可能會遇到類似的情形,而且各個單元之
間的層次關系會更復雜。所以自上而下測試法的缺點對單元測試造成的不利影響會隨著被測
單元之間復雜的聯系而加深。
4. 總結
一個自上而下的測試策略成本將高于基于分離的測試策略,這取決于頂層單元下層單元的復
雜程度,以及由于下層單元自身發生變化所帶來的顯著影響。對于單元測試來說自上而下的
組織方法不是一個好的選擇。然而,當各個組成單元已經被單獨測試的情況下,用自上而下
法進行單元的集成測試是個不錯的手段。
三、自下而上法
1. 詳述
在自下而上的單元測試中,被測單元與調用被測單元的單元是分開測試的,但是測試時所使
用的是真實的被調用單元。測試時最底層的單元首先被測試,這樣就方便了對高層次單元的
測試。然后使用前面已經被測試過的被調用單元來測試其他的單元。重復這個過程直到最高
層的單元被測試為止。自下而上法需要測試驅動,但是不需要測試樁。圖3.1 說明了測試D
單元時需要的測試驅動和已測單元的情況,假設單元E、F、G、H、I 和J 已經通過自下而上
法進行了測試。
圖3.1 自下而上測試法
圖3.1 顯示了一個程序的單元測試的測試計劃,該計劃使用了基于自下而上的組織方法,其
過程如下:
步驟(1)
(注意在測試步驟中測試的順序不是最主要的,步驟1 中的所有測試可以同
步進行)測試單元H,在調用H 單元的E 單元處使用一個測試驅動;測試單元I,在調用I 單
元的E 單元處使用一個測試驅動;
測試單元J,在調用J 單元的E 單元處使用一個測試驅動;
測試單元F,在調用F 單元的D 單元處使用一個測試驅動;
測試單元G,在調用G 單元的D 單元處使用一個測試驅動;
測試單元B,在調用B 單元的A 單元處使用一個測試驅動;
測試單元C,在調用C 單元的A 單元處使用一個測試驅動;
步驟(2)
測試單元E,在調用E 單元的D 單元處使用一個測試驅動,再加上已測試過的單元H、I 和J。
步驟(3)
測試單元D,在調用D 單元的A 單元處使用一個測試驅動,再加上已測試過的單元E、F、G
、H、I 和J。(如圖3.1 所示)
步驟(4)
測試單元A,使用已測試過的單元B、C、D、E、F、G、H、I 和J。
2. 優點
和自上而下法一樣,自下而上單元測試法提供了一種比軟件集成階段更早的單元集成。自下
而上單元測試同樣也是真正意義上的單元測試和軟件集成策略的結合。因為不需要測試樁,
所以所有的測試用例都由測試驅動控制。這樣就使得低層次單元附近的單元測試相對簡單些
。(但是,高層次單元的測試可能會變得很復雜。)在使用自下而上法測試時,測試用例的
編寫可能只需要功能性的設計信息,不需要結構化的設計信息(盡管結構化設計信息可能有
利于實現測試的全覆蓋)。所以當詳細的設計文檔缺乏結構化的細節時,自下而上的單元測
試就變得十分有用處。自下而上單元測試法提供了一種低層次功能性的集成,而較高層次的
功能隨著單元測試過程的進行按照單元層次關系逐層增加。這就使得自下而上單元測試很容
易地與測試對象相兼容。
3. 缺點
隨著測試逐層推進,自下而上單元測試變得越來越復雜,隨之而來的是開發和維護的成本越
來越高昂,同樣要實現好的結構覆蓋也變得越來越困難。低層單元的變化經常影響其上層單
元的測試。例如:想象一下H 單元發生變化的情況。很明顯,對H 單元的測試不得不發生變
化和重新進行。另外,對于A、D 和E 單元的測試來說,因為它們共同使用了已測試過的H
單元,所以它們的測試也不得不重做。作為H 單元發生變化的后果,這些測試本身可能也要
進行改變,即使單元A、D 和E 實際上并沒有發生變化。這就導致了當變化發生時,產生了
與重新測試有關的高額代價,以及高額的維護成本和整個軟件生命周期成本的提高。單元測
試的順序取決于單元的層次關系,較高層次的單元必須要等到較低層次單元通過測試后才能
進行測試,所以就形成了“長瘦”型的單元測試階段。最先被測試的單元是最后被設計的單元
,所以單元測試不能與軟件生命周期的詳細設計階段重疊。如圖2.2 所示的例子程序中各個
單元之間的層次關系十分簡單,在實際的編程過程中可能會遇到類似的情形,而且各個單元
之間的層次關系會更復雜。與自上而下測試法一樣,自下而上測試法的缺點會隨著被測單元
之間復雜的聯系而放大。
4. 總結
自下而上組織法對于單元測試來說是個比較好的手段,特別是當測試對象和重用情況時。然
而,自下而上方法偏向于功能性測試,而不是結構化測試。對于很多標準所需要的高集成度
和安全的關鍵性應用,需要達到高層次的結構覆蓋,但自下而上法很難滿足這個要求。自下
而上單元測試法與很多軟件開發所要求的緊湊的時間計劃是相沖突的?偟膩碚f,一個自下
而上策略成本將高于基于分離的測試策略,這是因為單元層次結構中低層次單元以上單元的
復雜程度和它們發生變化所帶來的顯著影響。
四、分離法
1. 詳述
分離測試法是分開測試每一個單元,無論是被調用單元還是調用單元。被測單元可以按照任
意順序進行測試,因為被測單元不需要其他任何已測單元的支持。每一個單元的測試都需要
一個測試驅動,并且所有的被調用單元都要用測試樁代替。圖4.1 說明了測試單元D 時需要
的測試驅動和測試樁的情況。
圖4.1 分離測試法
圖4.1 顯示了某個程序中一個單元的測試計劃,該計劃基于分離組織方法的策略,只需要如
下所示的一步:
步驟(1)
(注意該測試計劃只有一步。測試的順序不是最主要的,所有的測試可以同步進行。)
測試A 單元,使用一個測試驅動啟動測試,并且將B、C 和D 單元換成測試樁;
測試B 單元,在A 單元處使用一個測試驅動來調用B 單元;
測試C 單元,在A 單元處使用一個測試驅動來調用C 單元;
測試D 單元,在A 單元處使用一個測試驅動來調用D 單元,并且將E、F和G 單元換成測試樁
(如圖3.1 所示);
測試E 單元,在D 單元處使用一個測試驅動來調用E 單元,并且將H、I和J 單元換成測試樁
;
測試F 單元,在D 單元處使用一個測試驅動來調用F 單元;
測試G 單元,在D 單元處使用一個測試驅動來調用G 單元;
測試H 單元,在E 單元處使用一個測試驅動來調用H 單元;
測試I 單元,在E 單元處使用一個測試驅動來調用I 單元;
測試J 單元,在E 單元處使用一個測試驅動來調用J 單元。
2. 優點
徹底地測試一個分離的單元是很容易做到的,單元測試將其從與其它單元之間復雜的關系中
分離了出來。分離測試是最容易實現良好的結構性覆蓋的方法,并且實現良好結構性覆蓋的
困難程度與確定某一個單元在單元層次中所處位置的難易度沒有什么不同。
因為每一次只測試一個單元,所以該方法中所使用的測試驅動比自下而上法中所使用的測試
驅動簡單,該方法中所使用的測試樁比自上而下法中使用的測試樁簡單。由于采用了分離的
方法進行單元測試,被測單元之間沒有依賴關系,所以單元測試階段可以和詳細設計階段,
以及軟件生命周期的代碼編寫階段重疊。所有單元都能同步測試,形成了單元測試階段“短
而寬”的特點。這有利于通過擴大團隊規模的手段縮短整個軟件開發的時間。分離測試法另
外一個優點是去除了測試單元之間的內部依賴關系,所以當一個單元發生變化時只需要改變
那個發生變化的測試單元,而對其它測試單元沒有任何影響。由此可以看出分離組織法的成
本要低于自下而上組織法和自上而下組織法,特別是當發生變化時其效果更加明顯。分離法
提供了一種與集成測試不同的單元測試分離手段,它允許開發人員在軟件生命周期的單元測
試階段專心致力于單元測試工作,而在軟件生命周期的集成測試階段專心致力于集成測試工
作。只有分離法是純粹意義上適用于單元測試的方法,自上而下測試法和自下而上測試法適
用于單元測試和集成階段的混合過程。與自上而下法和自下而上法不同的是,用分離法進行
的單元測試,被測單元不會受到與其關聯的其它任何單元的影響。
3. 缺點
用分離法進行單元測試最主要的缺點是它不能提供一個早期的單元集成。這必須要等到軟件
生命周期的集成階段才能做到。(這很難說是一個真正的缺點)用分離法進行單元測試時需
要結構設計信息和使用測試樁、測試驅動。這會導致在測試靠近底層的單元時,所花費成本
要高于自下而上法。然而,這個缺陷可以通過簡化層次較高的單元的測試,以及每個單元每
次發生變化時的較低花費得到補償。
4. 總結
用分離法進行單元測試是最合適的選擇。在加上適當的集成策略作為補充,將會縮短軟件開
發時間所占比例和降低開發費用,這個優勢將會貫穿整個軟件開發過程和軟件生命周期。按
照分離法進行單元測試時,被測單元可以按照自上而下或者自下而上的順序進行集成,或者
集成為任何便利的群組和群組的結合。然而,一個自下而上的集成方式是與目前流行的面向
對象和面向對象的設計最相兼容的策略。分離法單元測試是實現高層次結構覆蓋的最佳手段
,而高層次結構覆蓋對于很多標準所要求的高完善性和安全的關鍵性應用來說是至關重要。
在通過單元測試完成了所有實現好的結構覆蓋的困難工作的基礎上,集成測試就可以集中于
全面的功能測試和單元交互的測試。
五、使用AdaTEST 和Cantata
一個單元的測試在整個軟件生命周期中要重復進行很多次,無論是在開發階段還是維護過程
中。一些測試工具如:AdaTEST 和Cantata,可以用于一些易于重復進行和花費較少的自動化
單元測試中,這樣可以有效降低人為因素帶來的風險。AdaTEST 和Cantata 測試腳本由一個
測試驅動和一個樁的集合(可選的)組成。AdaTEST 和Cantata 可以用于本文所介紹的任何
單元測試的組織方法,或者這些方法的任意組合,使得開發人員可以采用最適合于項目應用
的測試策略。IPL 提供了兩篇相關論文,如下所示:
“Achieving Testability when using Ada Packaging and Data Hiding Methods”“Testing C++ Objects”
論文“Testing C++ Objects”同樣詳細討論了在用自下而上法進行單元測試時,分離的類和層次
等級的約束是如何引發問題的。文章介紹了分離單元測試法是如何成為唯一實用的處理分離
的類和層次等級約束的途徑。
1、結論
在實踐中,將任何一種方法專門用于進行單元測試是不可能的。通常,分離單元測試法要通
過一些自下而上的測試加以修改,將被調用單元用測試樁和已測的實際單元的混合體來表示
。例如,直接使用一個數學函數更有實際意義,因為它已被測試并且不大可能發生改變。
一些建議的策略如下:
1、基于你的分離法的單元測試策略,繼而自下而上的集成被測單元。
2、折中法,即自下而上的通過合并一些便于合并的單元,(例如:使用實際的操作符,數
學函數,字符串操作等。)但是要記住潛在的變化帶來的影響。無論是進行單元測試,還是
隨著所測單元發生變化時重新測試和維護,同時也為了滿足軟件的可靠性而促進徹底的測試
覆蓋,這些都將導致成本的最低化。請記住,單元測試是指測試每一個單元,而集成測試是
指測試被測單元之間的交互關系。
延伸閱讀
文章來源于領測軟件測試網 http://www.kjueaiud.com/