單元測試的意義、做法、經(jīng)驗
@(工作日志)[C++|單元測試]
[TOC]
單元測試的意義
作為軟件系統(tǒng)的最小組成單位,單元測試具有以下屬性:
- 它是由一個程序員完成的。
- 它有一個詳細的設(shè)計說明,包括輸入定義、輸出定義和加工說明。
- 它是一個可識別的看得見的程序組成部分,并容易被組合成程序。
- 能被單獨地匯編和測試。
- 它的規(guī)模比較小,邏輯比較簡單。
因此單元測試具有以下意義:
單元測試集中注意力于程序的基本組成部分,首先保證每個單元測試通過,才能使下一步把單元組裝成部件并測試其正確性具有基礎(chǔ)。單元是整個軟件的構(gòu)成基礎(chǔ),像硬件系統(tǒng)中的零部件一樣,只有保證零部件的質(zhì)量,這個設(shè)備的質(zhì)量才有基礎(chǔ),單元的質(zhì)量也是整個軟件質(zhì)量的基礎(chǔ)。因此,單元測試的效果會直接影響軟件的后期測試,最終在很大程度上影響到產(chǎn)品的質(zhì)量。
單元測試可以平行開展,這樣可以使多人同時測試多個單元,提高了測試的效率。
單元規(guī)模較小,復雜性較低,因而發(fā)現(xiàn)錯誤后容易隔離和定位,有利于調(diào)試工作。
單元的規(guī)模和復雜性特點,使單元測試中可以使用包括白盒測試的覆蓋分析在內(nèi)的許多測試技術(shù),能夠進行比較充分細致的測試,是整個程序測試滿足語句覆蓋和分支覆蓋要求的基礎(chǔ)。
單元測試的測試效果是最顯而易見的。做好單元測試,不僅后期的系統(tǒng)集成聯(lián)調(diào)或集成測試和系統(tǒng)測試會很順利,節(jié)約很多時間;而且在單元測試過程中能發(fā)現(xiàn)一些很深層次的問題,同時還會發(fā)現(xiàn)一些很容易發(fā)現(xiàn)而在集成測試和系統(tǒng)測試很難發(fā)現(xiàn)的問題;更重要的是單元測試不僅僅是證明這些代碼做了什么,是如何做的,而且證明是否做了它該做的事情而沒有做不該做的事情。
單元測試的好與壞不僅直接關(guān)系到測試成本(因為如果單元測試中易發(fā)現(xiàn)的問題拖到后期測試發(fā)現(xiàn),那么其成本將成倍數(shù)上升),而且也會直接影響到產(chǎn)品質(zhì)量,因為可能就是由于代碼中的某一個小錯誤就導致了整個產(chǎn)品的質(zhì)量降低一個指標,或者導致更嚴重的后果。
事實上
- 單元測試是一種驗證行為—— 測試和驗證程序中每一項功能的正確性,為以后的開發(fā)提供支持;
- 單元測試是一種設(shè)計行為—— 編寫單元測試將使我們從調(diào)用者觀察、思考,特別是要先考慮測試,這樣就可把程序設(shè)計成易于調(diào)用和可測試的,并努力降低軟件中的耦合,還可以使編碼人員在編碼時產(chǎn)生預測試,將程序的缺陷降低到最??;
- 單元測試是一種編寫文檔的行為—— 是展示函數(shù)或類如何使用的最佳文檔;
- 單元測試具有回歸性—— 自動化的單元測試有助于進行回歸測試。
(參考:淺談單元測試的意義)
單元測試的內(nèi)容
單元測試由一組獨立的測試構(gòu)成,每個測試針對軟件中的一個單獨的程序單元。單元測試并非檢查程序單元之間是否能夠合作良好,而是檢查單個程序單元行為是否正確。
在單元測試時,測試人員根據(jù)詳細設(shè)計說明書和源程序清單,了解到該模塊的I/O條件和模塊的邏輯結(jié)構(gòu),主要采用白盒測試的測試用例,輔之以黑盒測試的測試用例,使之對任何合理和不合理的輸入都要能鑒別和響應。這就要求對程序所有的局部和全局的數(shù)據(jù)結(jié)構(gòu)、外部接口和程序代碼的關(guān)鍵部分進行桌面檢查和代碼審查。
在單元測試中進行的測試工作主要在5個方面對被測模塊進行檢查:
模塊接口測試
在單元測試開始時,應該對通過所有被測模塊的數(shù)據(jù)流進行測試。如果數(shù)據(jù)不能正常地輸入及輸出,那么其他的全部測試都說明不了問題。Myers在關(guān)于軟件測試的書中為接口測試提出了一個檢查表:
- 模塊輸入?yún)?shù)的數(shù)目是否與模塊形式參數(shù)數(shù)目相同。
- 模塊各輸入的參數(shù)屬性與對應的形參屬性是否一致。
- 模塊各輸入的參數(shù)類型與對應的形參類型是否一致。
- 傳到被調(diào)用模塊的實參的數(shù)目是否與被調(diào)用模塊形參的數(shù)目相同。
- 傳到被調(diào)用模塊的實參的屬性是否與被調(diào)用模塊形參的屬性相同。
- 傳到被調(diào)用模塊的實參的類型是否與被調(diào)用模塊形參的類型相同。
- 引用內(nèi)部函數(shù)時,實參的次序和數(shù)目是否正確。
- 是否引用了與當前入口無關(guān)的參數(shù)。
- 用于輸入的變量有沒有改變。
- 在經(jīng)過不同模塊時,全局變量的定義是否一致。
- 限制條件是否以形參的形式傳遞。
- 使用外部資源時,是否檢查可用性并及時釋放資源,如內(nèi)存、文件、硬盤、端口等。
當模塊通過外部設(shè)備進行輸入/輸出操作時,必須擴展接口測試,附加如下的測試項目:
- 文件的屬性是否正確。
- Open與Close語句是否正確。
- 規(guī)定的格式是否與I/O語句相符。
- 緩沖區(qū)的大小與記錄的大小是否相配合。
- 在使用文件前,文件是否打開。
- 文件結(jié)束的條件是否安排好了。
- I/O錯誤是否檢查并做了處理。
- 在輸出信息中是否有文字錯誤。
局部數(shù)據(jù)結(jié)構(gòu)測試
模塊的局部數(shù)據(jù)結(jié)構(gòu)是最常見的錯誤來源,應設(shè)計測試用例以檢查以下各種錯誤:
- 不正確或不一致的數(shù)據(jù)類型說明。
- 使用尚未賦值或尚未初始化的變量。
- 錯誤的初始值或錯誤的默認值。
- 變量名拼寫錯或書寫錯——使用了外部變量或函數(shù)。
- 不一致的數(shù)據(jù)類型。
- 全局數(shù)據(jù)對模塊的影響。
- 數(shù)組越界。
- 非法指針。
路徑測試
檢查由于計算錯誤、判定錯誤、控制流錯誤導致的程序錯誤。由于在測試時不可能做到窮舉測試,所以在單元測試時要根據(jù)“白盒”測試和“黑盒”測試用例設(shè)計方法設(shè)計測試用例,對模塊中重要的執(zhí)行路徑進行測試。重要的執(zhí)行路徑指那些處在完成單元功能的算法、控制、數(shù)據(jù)處理等重要位置的執(zhí)行路徑,也指由于控制較復雜而易錯的路徑,有選擇地對執(zhí)行路徑進行測試是一項重要的任務。應當設(shè)計測試用例查找由于錯誤的計算、不正確的比較或不正常的控制流而導致的錯誤,對基本執(zhí)行路徑和循環(huán)進行測試可發(fā)現(xiàn)大量的路徑錯誤。
在路徑測試中,要檢查的錯誤有:
- 死代碼
- 錯誤的計算優(yōu)先級
- 算法錯誤
- 混用不同類的操作
- 初始化不正確
- 精度錯誤——比較運算錯誤、賦值錯誤
- 表達式的不正確符號——>、>=;=、==、!=
- 循環(huán)變量的使用錯誤——錯誤賦值等
比較操作和控制流向緊密相關(guān),測試用例設(shè)計需要注意發(fā)現(xiàn)比較操作的錯誤:
- 不同數(shù)據(jù)類型的比較。
- 不正確的邏輯運算符或優(yōu)先次序。
- 因浮點運算精度問題而造成的兩值比較不等。
- 關(guān)系表達式中不正確的變量和比較符。
- “差 1 錯”,即不正常的或不存在的循環(huán)中的條件。
- 當遇到發(fā)散的循環(huán)時無法跳出循環(huán)。
- 當遇到發(fā)散的迭代時不能終止循環(huán)。
- 錯誤的修改循環(huán)變量。
錯誤處理測試
錯誤處理路徑是可能引發(fā)錯誤處理的路徑及進行錯誤處理的路徑,錯誤出現(xiàn)時錯誤處理程序重新安排執(zhí)行路線,或通知用戶處理,或干脆停止執(zhí)行使程序進入一種安全等待狀態(tài)。測試人員應意識到,每一行程序代碼都可能執(zhí)行到,不能自己認為錯誤發(fā)生的概率很小而不去進行測試。一般軟件錯誤處理測試應考慮下面幾種可能的錯誤:
- 出錯的描述是否難以理解,是否能夠?qū)﹀e誤定位。
- 顯示的錯誤與實際的錯誤是否相符;
- 對錯誤條件的處理正確與否;
- 在對錯誤進行處理之前,錯誤條件是否已經(jīng)引起系統(tǒng)的干預等。
在進行錯誤處理測試時,要檢查如下內(nèi)容:
- 在資源使用前后或其他模塊使用前后,程序是否進行錯誤出現(xiàn)檢查。
- 出現(xiàn)錯誤后,是否可以進行錯誤處理,如引發(fā)錯誤、通知用戶、進行記錄。
- 在系統(tǒng)干預前,錯誤處理是否有效,報告和記錄的錯誤是否真實詳細。
邊界測試
邊界測試是單元測試中最后的任務。軟件常常在邊界上出錯,例如,在一個程序段中有一個n次循環(huán),當?shù)竭_第n次循環(huán)時就可能會出錯;或者在一個有n個元素的數(shù)組中,第n個元素時是很容易出錯的。因此,要特別注意數(shù)據(jù)流、控制流中剛好等于、大于或小于確定的比較值時出錯的可能性。對這些地方要仔細地選擇測試用例,認真加以測試。
此外,如果對模塊性能有要求的話,還要專門進行關(guān)鍵路徑測試,以確定最壞情況下和平均意義下影響運行時間的因素。下面是邊界測試的具體要檢查的內(nèi)容:
- 普通合法數(shù)據(jù)是否正確處理。
- 普通非法數(shù)據(jù)是否正確處理。
- 邊界內(nèi)最接近邊界的(合法)數(shù)據(jù)是否正確處理。
- 邊界外最接近邊界的(非法)數(shù)據(jù)是否正確處理等。
- 在n次循環(huán)的第0次、第1次、第n次是否有錯誤。
- 運算或判斷中取最大最小值時是否有錯誤;
- 數(shù)據(jù)流、控制流中剛好等于、大于、小于確定的比較值時是否出現(xiàn)錯誤。
單元測試的要求
為了使單元測試能充分細致地展開,應在實施單元測試中遵守下述要求:
語句覆蓋達到99%
語句覆蓋指被測單元中每條可執(zhí)行語句都被測試用例所覆蓋。語句覆蓋是強度最低的覆蓋要求,要考慮語句覆蓋的意義,只要想象一下用一段從沒執(zhí)行過的程序控制龐大的飛行器升上天空,然后設(shè)法使它精確入軌,這種輕率簡直就是荒唐。實際測試中,不一定能做到每條語句都執(zhí)行到。第一,存在“死碼”,即由于程序設(shè)計錯誤在任何情況下都不可能執(zhí)行到的代碼。第二,不是“死碼”,但是由于要求的測試輸入及條件非常難達到或單元測試的條件所限,使得代碼沒有得到運行。因此,在可執(zhí)行語句未得到執(zhí)行時,要深入程序作詳細的分析。如果是屬于以上兩種情況,則可以認為完成了覆蓋,但是對于后者,如果可能一定要盡量測試到,如果以上兩者都不是,則是因為測試用例設(shè)計不充分,需要再設(shè)計測試用例。
分支覆蓋達到100%
分支覆蓋指分支語句取真值和取假值各一次,分支語句是程序控制流的重要處理語句,在不同流向上測試可以驗證這些控制流向的正確性。分支覆蓋使這些分支產(chǎn)生的輸出都得到驗證,提高測試的充分性。
覆蓋錯誤處理路徑
單元的軟件特性覆蓋
軟件的特性包括功能、性能、屬性、設(shè)計約束、狀態(tài)數(shù)目、分支的行數(shù)等。
對試用額定數(shù)據(jù)值、奇異數(shù)據(jù)值和邊界值的計算進行檢驗,用假想的數(shù)據(jù)類型和數(shù)據(jù)值運行,測試排斥不規(guī)則輸入的能力。
單元測試通常是由編寫程序的人自己完成的,但是項目負責人應當關(guān)心測試的結(jié)果。所有的測試用例和測試結(jié)果都是模塊開發(fā)的重要資料,需妥善保存。
(參考:詳細講解單元測試的內(nèi)容)
實踐經(jīng)驗
單元測試的時機很重要
無非兩種:
- 在具體實現(xiàn)代碼之前,這就是所謂的測試驅(qū)動開發(fā);
- 與具體實現(xiàn)代碼同步進行,正是大部分人采用的方式。
那種事后單元測試,基本是沒用的。當然有一種例外:要對沒有單元測試的既有代碼進行維護和改造,這時候需要為既有代碼追加單元測試,但是這也得建立在充分調(diào)查理解需求的基礎(chǔ)上才能進行。
單元測試的執(zhí)行者的角色
單元測試應當由具體實現(xiàn)代碼的開發(fā)者進行,也就是說每個開發(fā)人員都應當同時對自己負責具體實現(xiàn)代碼和單元測試代碼負責。
這里也存在同第一條中的例外的情況。
單元測試應當突出重點
應當對那些重點部分重點關(guān)照,主要有:
- 邏輯復雜的
- 容易出錯的
- 不易理解的,即使是自己過段時間也會遺忘的,看不懂自己的代碼,單元測試代碼有助于理解代碼的功能和需求
- 后期需求變更可能性相對比較大的,這樣后期需求更變修改代碼之后就不用太擔心寫的代碼對不對以及是否破壞既存代碼邏輯了
單元測試也應注重質(zhì)量,不要寫無用的測試代碼
寫沒有實際用處的單元測試不如不寫,正如注釋,寫沒有意義的注釋也不如不寫,既降低代碼可讀性又容易誤人子弟。
敏捷開發(fā)的宗旨是“以人為本”,是解放,而不是壓迫
單元測試雖說從長期來看,可以提高代碼質(zhì)量、減少維護成本、降低重構(gòu)難度,擁有眾多好處。但是從短期來看,肯定是會加大工作量的,也就是說需要投入更多的人力成本,如果只想得到好處,卻不愿投入相應的成本,還是不要搞單元測試了——那只會導致開發(fā)人員更多的加班,不會產(chǎn)生好的效果。因為整個敏捷開發(fā)的宗旨,是“以人為本”,而不是從開發(fā)人員身上榨出更多的油水。單元測試是為了解放開發(fā)人員,而不是壓迫,是為了從長遠的角度減輕開發(fā)人員的工作量!
最后要說的是:
那種為了單元測試而單元測試的愚蠢行為應當立即停止。
那種只是想把單元測試作為一項面子工程的行為更應當停止(官場的種種壞習慣不應該在思想純潔的程序員當中流行)。
那些對單元測試沒有深入理解,只是希望今后能冠以“單元測試覆蓋率100%”榮譽頭銜的團隊,應該立即停止這種想法。
單元測試不應當過于重視覆蓋率,而應該在需要的時候?qū)憜卧獪y試。何時寫,怎么寫,都需要建立在開發(fā)者已經(jīng)對單元測試有深刻理解的基礎(chǔ)上。
單元測試也是一把雙刃劍,要用得好它才能發(fā)光發(fā)熱,產(chǎn)生強大的正能量,請不要把它當作“龍泉寶劍”掛在自家的玄關(guān)辟邪。
(參考:對單元測試的一點感悟)