天天看點

代碼覆寫率 語句覆寫 判定覆寫 條件覆寫 路徑覆寫

在做單元測試時,代碼覆寫率常常被拿來作為衡量測試好壞的名額,甚至,用代碼覆寫率來考核測試任務完成情況,比如,代碼覆寫率必須達到80%或 90%。于是乎,測試人員費盡心思設計案例覆寫代碼。用代碼覆寫率來衡量,有利也有有弊。本文我們就代碼覆寫率展開讨論,也歡迎同學們踴躍評論。

首先,讓我們先來了解一下所謂的“代碼覆寫率”。我找來了所謂的定義:

代碼覆寫率 = 代碼的覆寫程度,一種度量方式。

上面簡短精悍的文字非常準确的描述了代碼覆寫率的含義。而代碼覆寫程度的度量方式是有很多種的,這裡介紹一下最常用的幾種:

1. 語句覆寫(StatementCoverage)

又稱行覆寫(LineCoverage),段覆寫(SegmentCoverage),基本塊覆寫(BasicBlockCoverage),這是最常用也是最常見的一種覆寫方式,就是度量被測代碼中每個可執行語句是否被執行到了。這裡說的是“可執行語句”,是以就不會包括像C++的頭檔案聲明,代碼注釋,空行,等等。非常好了解,隻統計能夠執行的代碼被執行了多少行。需要注意的是,單獨一行的花括号{} 也常常被統計進去。語句覆寫常常被人指責為“最弱的覆寫”,它隻管覆寫代碼中的執行語句,卻不考慮各種分支的組合等等。假如你的上司隻要求你達到語句覆寫,那麼你可以省下很多功夫,但是,換來的确實測試效果的不明顯,很難更多地發現代碼中的問題。

這裡舉一個不能再簡單的例子,我們看下面的被測試代碼:

int foo(int a, int b)

{

   return  a / b;

}

假如我們的測試人員編寫如下測試案例:

TeseCase: a = 10, b = 5

測試人員的測試結果會告訴你,他的代碼覆寫率達到了100%,并且所有測試案例都通過了。然而遺憾的是,我們的語句覆寫率達到了所謂的100%,但是卻沒有發現最簡單的Bug,比如,當我讓b=0時,會抛出一個除零異常。

正因如此,假如上面隻要求測試人員語句覆寫率達到多少的話,測試人員隻要鑽鑽空子,專門針對如何覆寫代碼行編寫測試案例,就很容易達到主管的要求。當然了,這同時說明了幾個問題:

    1.主管隻使用語句覆寫率來考核測試人員本身就有問題。

    2.測試人員的目的是為了測好代碼,鑽如此的空子是缺乏職業道德的。 

    3.是否應該采用更好的考核方式來考核測試人員的工作? 

為了尋求更好的考核标準,我們必須先了解完代碼覆寫率到底還有哪些,如果你的主管隻知道語句覆寫,行覆寫,那麼你應該主動向他介紹還有更多的覆寫方式。比如:

2. 判定覆寫(DecisionCoverage)

又稱分支覆寫(BranchCoverage),所有邊界覆寫(All-EdgesCoverage),基本路徑覆寫(BasicPathCoverage),判定路徑覆寫(Decision-Decision-Path)。它度量程式中每一個判定的分支是否都被測試到了。這句話是需要進一步了解的,應該非常容易和下面說到的條件覆寫混淆。是以我們直接介紹第三種覆寫方式,然後和判定覆寫一起來對比,就明白兩者是怎麼回事了。

3. 條件覆寫(ConditionCoverage)

它度量判定中的每個子表達式結果true和false是否被測試到了。為了說明判定覆寫和條件覆寫的差別,我們來舉一個例子,假如我們的被測代碼如下:

代碼覆寫率 語句覆寫 判定覆寫 條件覆寫 路徑覆寫

int foo(int a, int b)

{

    if (a < 10 || b < 10) // 判定

    {

        return 0; // 分支一

    }

    else

    {

        return 1; // 分支二

    }

}

代碼覆寫率 語句覆寫 判定覆寫 條件覆寫 路徑覆寫

設計判定覆寫案例時,我們隻需要考慮判定結果為true和false兩種情況,是以,我們設計如下的案例就能達到判定覆寫率100%:

TestCaes1: a = 5, b = 任意數字  覆寫了分支一

TestCaes2: a = 15, b = 15          覆寫了分支二

設計條件覆寫案例時,我們需要考慮判定中的每個條件表達式結果,為了覆寫率達到100%,我們設計了如下的案例:

TestCase1: a = 5, b = 5       true,  true

TestCase4: a = 15, b = 15   false, false

通過上面的例子,我們應該很清楚了判定覆寫和條件覆寫的差別。需要特别注意的是:條件覆寫不是将判定中的每個條件表達式的結果進行排列組合,而是隻要每個條件表達式的結果true和false測試到了就OK了。是以,我們可以這樣推論:完全的條件覆寫并不能保證完全的判定覆寫。比如上面的例子,假如我設計的案例為:

TestCase1: a = 5, b = 15  true,  false   分支一

TestCase1: a = 15, b = 5  false, true    分支一

我們看到,雖然我們完整的做到了條件覆寫,但是我們卻沒有做到完整的判定覆寫,我們隻覆寫了分支一。上面的例子也可以看出,這兩種覆寫方式看起來似乎都不咋滴。我們接下來看看第四種覆寫方式。

4. 路徑覆寫(PathCoverage)

又稱斷言覆寫(PredicateCoverage)。它度量了是否函數的每一個分支都被執行了。 這句話也非常好了解,就是所有可能的分支都執行一遍,有多個分支嵌套時,需要對多個分支進行排列組合,可想而知,測試路徑随着分支的數量指數級别增加。比如下面的測試代碼中有兩個判定分支:

代碼覆寫率 語句覆寫 判定覆寫 條件覆寫 路徑覆寫

int foo(int a, int b)

{

    int nReturn = 0;

    if (a < 10)

    {// 分支一

        nReturn += 1;

    }

    if (b < 10)

    {// 分支二

        nReturn += 10;

    }

    return nReturn;

}

代碼覆寫率 語句覆寫 判定覆寫 條件覆寫 路徑覆寫

對上面的代碼,我們分别針對我們前三種覆寫方式來設計測試案例:

a. 語句覆寫

TestCase a = 5, b = 5   nReturn = 11

 語句覆寫率100%

b. 判定覆寫

TestCase1 a = 5,   b = 5     nReturn = 11

TestCase2 a = 15, b = 15   nReturn = 0

判定覆寫率100% 

c. 條件覆寫

TestCase1 a = 5,   b = 15   nReturn = 1

TestCase2 a = 15, b = 5     nReturn = 10

條件覆寫率100% 

我們看到,上面三種覆寫率結果看起來都很酷!都達到了100%!主管可能會非常的開心,但是,讓我們再去仔細的看看,上面被測代碼中,nReturn的結果一共有四種可能的傳回值:0,1,10,11,而我們上面的針對每種覆寫率設計的測試案例隻覆寫了部分傳回值,是以,可以說使用上面任一覆寫方式,雖然覆寫率達到了100%,但是并沒有測試完全。接下來我們來看看針對路徑覆寫設計出來的測試案例:

代碼覆寫率 語句覆寫 判定覆寫 條件覆寫 路徑覆寫

TestCase1 a = 5,    b = 5     nReturn = 0

TestCase2 a = 15,  b = 5     nReturn = 1

TestCase3 a = 5,    b = 15   nReturn = 10

TestCase4 a = 15,  b = 15   nReturn = 11

路徑覆寫率100% 

代碼覆寫率 語句覆寫 判定覆寫 條件覆寫 路徑覆寫

太棒了!路徑覆寫将所有可能的傳回值都測試到了。這也正是它被很多人認為是“最強的覆寫”的原因了。

還有一些其他的覆寫方式,如:循環覆寫(LoopCoverage),它度量是否對循環體執行了零次,一次和多餘一次循環。剩下一些其他覆寫方式就不介紹了。

總結

通過上面的學習,我們再回頭想想,覆寫率資料到底有多大意義。我總結了如下幾個觀點,歡迎大家讨論:

a. 覆寫率資料隻能代表你測試過哪些代碼,不能代表你是否測試好這些代碼。(比如上面第一個除零Bug)

b. 不要過于相信覆寫率資料。

c. 不要隻拿語句覆寫率(行覆寫率)來考核你的測試人員。

d. 路徑覆寫率 > 判定覆寫 > 語句覆寫

e. 測試人員不能盲目追求代碼覆寫率,而應該想辦法設計更多更好的案例,哪怕多設計出來的案例對覆寫率一點影響也沒有。

繼續閱讀