無論是單元測試、API測試還是功能性測試,最終都是調用了產品的代碼;如何評價這些測試的效率,是否真正全部或者大部分覆蓋了產品的代碼,這個時候,程式碼涵蓋範圍(code coverage)就是一個比較有價值的參考指標了。
通常,程式碼涵蓋範圍用在如下幾個方面
- 找出程式中沒有被測試代碼執行到的地方;
- 增加新的測試代碼,以提高程式碼涵蓋範圍;
- 分析測試代碼的效率,以便設計出更有效測試代碼或測試案例
程式碼涵蓋範圍常用的指標
語句覆蓋(statement coverage):語句覆蓋是指程式的每一行代碼是否都被覆蓋到;語句覆蓋是最常用的一種程式碼涵蓋範圍指標,也非常簡單。但是對一些控制結構的代碼而言,它不能真正表示是否完全覆蓋到,如下代碼:
int* p=NULL;if(condition) p=&variable;*p=123;
在以上代碼中,如果condition為true,這時,語句覆蓋率就為100%。而其實當condtion為false,會導致null 指標被賦值的執行階段錯誤。
決策覆蓋(decision coverage):決策覆蓋是指在控制結構的代碼塊,如if, while中的整個bool運算式是否在false和true條件下,各被執行一次。決策覆蓋不考慮組成該條件運算式的各個子條件是否被完全覆蓋到。於是,通常對於c/c++/java語言,由於short circuit operators,對於一個由多個條件以與關係組成的一個條件運算式,當出現第一個條件被檢測為false時,其後所有的條件都不會被執行了。考慮一下代碼:
if(condition1 && (condition2 || function1())) statement1;else statement2;
在condtion1為false和conditon1為true,condition2為true,其決策覆蓋率為100%,而function1中的代碼從未被執行。決策覆蓋也叫分支覆蓋(branch coverage)。
條件覆蓋(condition coverage):條件覆蓋是指每個條件都必須有true和false的情況,這裡一個條件是一個邏輯操作符的運算元,但是不含該邏輯操作符。注意和決策覆蓋的區別,如
bool f(bool e){ return false; }bool a[2]={false, false};if(f(a&&b)) ...if(a[int(a&&b)]) ...if((a&&b)?false:false) ...
該代碼塊的3個if條件中,一共包含2個條件a,b;共四種情況a=true, a=false, b=true, b=false;我們只需取a,b分別為true和false的組合(a=true, b=false和a=false, b=true)即可使條件覆蓋為100%;而實際上,無論a,b取什麼值,3個if語句的分支始終都返回false。
多重條件覆蓋(multiple condition coverage):多重條件覆蓋是指各個條件的每個可能的組合是否被檢查到。多重條件覆蓋中的各個條件組合是一種排列關係,而不是組合。通常多重條件覆蓋的測試案例設計都非常複雜,因為在考慮到各個條件之間的排列關係時,其數量本身就多,而且還要考慮關係之間的相互影響,剔除無效的排列。
條件/決策覆蓋(condition/decision coverage):條件決策覆蓋是條件覆蓋和決策覆蓋的聯合運算,它具有比多重條件覆蓋簡單的優勢,同時又沒有條件覆蓋和決策覆蓋的缺點。
改進的條件/決策覆蓋(modified condition/decision coverage):其定義是程式中的每個入口和出口都至少被調用一次,一個決策的每個條件都至少有一次所有可能的輸出,程式中的每個決策都至少有一次所有可能的輸出,一個決策中的每個條件都能獨立的影響該決策的輸出(該條件改變,而其他條件保持不變)。
路徑覆蓋(path coverage):路徑決策指示函數中的每個可能的路徑是否被執行到。一條路徑是一個唯一的從函數入口到出口的分支序列。路徑覆蓋通常能比較徹底的進行測試,但是它也有兩個非常嚴重的缺陷:其一,路徑的數量是分支數量的幾何級數。例如,一個具有10個if語句的函數,需要1024個路徑測試;而我們再加一條if語句,則有2048個路徑需要測試。其次,由於資料相互關係,有些路徑是不可能被測試到的,如:
if(success) statement1;statement2;if(success) statement3;
這段程式碼封裝含4條路徑。而實際上,只有2條是可行的,success=false和success=true。
程式碼涵蓋範圍的其他指標
其他程式碼涵蓋範圍的指標還有函數覆蓋率(function coverage);調用覆蓋率(call coverage);迴圈覆蓋率(loop coverage)等等。
程式碼涵蓋範圍之間的關係
- 決策覆蓋通常是包含語句覆蓋的,因為每個分支的執行必然需要每個語句的執行。但是,如果函數的控制流程被throw,abort等語句打斷,那麼該規則就不適用了。
- 條件/決策覆蓋包含條件覆蓋和決策覆蓋。
- 路徑覆蓋包含決策覆蓋。
該如何利用程式碼涵蓋範圍的資料
通常,我們都希望用較少的時間設計出高效的測試案例。簡單的來說,高效的測試案例就是能儘可能多的發現程式中的問題。測試所花的時間、覆蓋率和檢測出的時間可參考下面的圖表:
即便程式碼涵蓋範圍為100%,我們只能說程式中的每行代碼都被執行到了。但是如果我們一定要有一個定量的指標,可參考下表:
| Coverage type |
without dependency injection |
with dependency injection |
| Function |
90% |
99% |
| Line |
75% |
95% |
ref: http://blogs.msdn.com/b/cellfish/archive/2008/06/16/code-coverage.aspx
Dependency Injection(DI)是一種設計模式,即把一個對象引入到一個類中,而不是依靠類來建立對象本身。具體可參考:http://msdn.microsoft.com/en-us/magazine/cc163739.aspx
值得注意的是:
- 不要一味的追求資料,為了提高代碼測試覆蓋率而去設計測試案例;
- 而要以代碼測試覆蓋率為參考,讓它來協助我們設計更加有意義,高效的測試案例;
如果你或者你的老闆一定要一個非常漂亮的數字,或者你用它來考量測試人員的績效,那麼對於.net平台上的程式,微軟有一個叫Pex的工具,可以協助你產生高程式碼涵蓋範圍的測試案例。具體可參考:
http://research.microsoft.com/en-us/projects/pex/
註:本文大部分參考Code Coverage Analysis,歡迎轉載,但請註明出處。謝謝。