【 聲明:著作權,歡迎轉載,請勿用於商業用途。 聯絡信箱:feixiaoxing @163.com】
很長時間以來,自己就想寫一篇關於單元測試的文章。但是由於自己在某些方面思考得不是很成熟,再加上前一段時間稍微有點忙,所以這個事情就一直這樣耽擱下來了。其實,朋友們在開發的時候都知道單元測試是個好東西,但是真正用於實踐,並且在開發中一直保持下去的卻是少數。雖然單元測試的架構很多,什麼CUnit,CxxUnit等存在很多現成的開原始碼架構,但是大家使用起來還不是很習慣。至於大家為什麼會對單元測試很抵觸,我想這主要有幾個方面的原因:(1)單元測試會在無形中增加自己的代碼開發量;(2)程式員們缺少軟體品質的意識,認為保證軟體品質是軟體測試部門的事情;(3)單元測試的效果無法在短期內有所體現,不如功能開發那樣立竿見影;(4)大家習慣了開發、編譯、調試、上機測試、修改這樣的傳統的開發方式;(5)項目至上而下缺少品質控制意識,片面追求開發速度、功能數量、入庫行數並過度依賴整合測試。
但是,這裡我想說的是每一個程式員都必須對自己的代碼負責,不管這段代碼是你設計的還是你維護的。單元測試就是一種很好的驗證你代碼品質的方法。無論是在設計測試案例、理解代碼設計、新功能開發、系統理解方面,單元測試都會對你有所協助。但是,不可否認,單元測試對個人的要求還是很高的,這就需要個人一點點去適應、去改變。
a)標頭檔模擬
在單元測試中,為了調用很多的底層函數,通常我們會對某些標頭檔進行模擬。這個時候,我們引用的函數完全是自己定義和設計的。但是,我們也不能為了現在的測試修改原來的標頭檔排布。所以,這個時候就需要對原有的標頭檔進行模擬。現在,我們假設原來會引用到一個data_type.h檔案,中間有我們需要的函式宣告,但是現在不需要了。這時候,我們就可以自己定義一個空的data_type.h檔案,添幾行代碼就可以了。
#ifndef _DATA_TYPE_H#define _DATA_TYPE_H#endif
b)資料處理流程和上層介面分開
我們在安排源檔案的時候,在安排函數分布的時候要注意一個基本的原則:資料和上層介面分開。在單元測試的時候,我們不太在乎曾經將這個資料的上層封裝形式是什麼。只有真正把資料從結構從釋放出來,形成一個獨立的處理檔案,這樣我們的測試才更方便、更有針對性。小函數、獨立函數、與介面分離的函數,這些都是我們在代碼開發中需要特別注意的。
c)底層驅動打樁處理
在真實的軟體模組中,我們的代碼是不可能獨立存在的,因此當前模組的代碼常常需要引用別的模組代碼。建立符合自己模組的樁函數,一方面可以提高代碼的開發效率,另外一方面也方便我們對自身的代碼進行測試。當然,底層驅動打樁函數是多種多樣的,某些配置類的函數我們可以象徵地輸出一行列印就可以了;某些函數我們可以利用測試端的一個相似函數代替即可;另外本地不存在的一些函數可能還需要我們真正編寫代碼模擬一把。
d)測試案例應該盡量和實際環境一致
為了驗證代碼的正確性,編寫測試案例當然是少不了的。但是,編寫測試案例並不是說越多越好。重複、低品質的測試案例只會浪費我們的測試資源。那麼,應該怎麼做呢?其實真實的運行情境才是我們所關注的。對於我們來說,最重要的就是把那些基礎功能、使用最多的功能、最容易犯錯的功能設計成測試案例,剩下的測試案例才是關於覆蓋率、效能方面的。
e)重視程式碼涵蓋範圍,更加重視功能覆蓋率
在開發中,很多開發人員甚至領導都會把代碼測試覆蓋率當作單元測試很重要的一個條件。誠然,高的程式碼涵蓋範圍固然能說明一些問題,但那不是問題的全部。我們進行單元測試的目的主要是為了驗證功能實現和設計是否一致,不是為了測試而測試。當然,在測試中我們可以模擬很多的條件,90%甚至更高的程式碼涵蓋範圍都是有可能的。但是,我們需要問一下自己,這些測試和最後的功能測試關係很大嗎?如果沒有這些測試,會影響最後的功能測試嗎?我們假設的這些單元測試條件在實際啟動並執行時候是真實存在的嗎?
f)多線程測試需要日誌儲存,在程式崩潰的時候產生dump檔案
有些功能的開發,是需要同時運行多個線程的。因此對於某些關鍵的配置,由於無法保證代碼的執行順序,我們需要對執行過程進行日誌記錄。如果程式在啟動並執行過程中發生了崩潰,我們也需要及時對關鍵資料進行記錄,儲存系統產生的dump檔案。
g)測試案例注意產生環境和清除環境
使用過CUnit的朋友想必對××_init和××_clean非常熟悉。××_init是為了給我們的測試案例構建測試環境,而××_clean則是對當前的測試環境進行清理,這樣不至於對下面一個測試造成影響。本質上說,CUnit乾的就是這麼一件事情,在測試案例運行前,自動調用你的**_init函數,執行結束後自動調用你的**_clean函數。如果你不使用現成的測試架構也沒有關係,但是你在測試代碼的時候也需要注意環境的產生和清理問題。
h)測試代碼也需要儲存、重構、模組、分層設計
測試代碼也不是一層不變的,有的時候為了適應代碼的重構需要,我們也需要對測試代碼進行重構處理。比如說,有些代碼我們是用來測試函數層級的準系統的,有些代碼我們是測試模組功能的,有的代碼我們是測試函數效能的,這些測試代碼都需要分開。另外,測試也會按照函數調用順序不斷增加測試的難度和複雜度的,所以測試代碼的分層設計也是十分必要的。
i)運行帶測試案例的實際版本
在版本release的時候,我們是絕不可能在實際版本中存在測試代碼的。但是在開發的時候,我們可以自己編譯產生帶測試代碼的版本。所以,我們需要做的就是保證我們的測試代碼不但可以在本地單元測試通過,還需要在實際環境通過。如果在這兩方面都能通過的話,那麼才能說明我們的測試是成功的,我們測試是有保障的,否則即使在本地做好了單元測試又有什麼意義呢?