白盒測試是軟體測試中的一種典型的場景。在這種場景下,測試人員能夠獲得程式碼,在對程式碼中的控制流、資料流等資訊的基礎上設計測試用例,並根據測試用例的實際運行結果來判斷程式是否符合期望、是否存在 bug、測試用例的品質高低等。
人們常用程式碼覆蓋率來衡量一個測試用例是否充分地測試了被測程式,一般而言,如果一個測試用例盡可能多地執行了程式碼,觸發了盡可能豐富的事件,那麼就說明測試越充分。如果沒有出現明顯的 bug,那麼程式的品質就越高,面對實際情況下使用者給定輸入的表現就越穩定(魯棒)。
一個程式的程式碼中通常包含迴圈、條件、函數呼叫等特殊結構,根據針對這些特殊結構設計不同的覆蓋規則可以衍生出不同的程式碼覆蓋標準,常見的有:
- 陳述覆蓋
- 分支覆蓋(判定覆蓋)
- 條件覆蓋
- 路徑覆蓋
以如下的程式為例:
陳述覆蓋#
陳述覆蓋(statement coverage)僅僅從執行的程式碼的行數來度量覆蓋率,覆蓋率的分母為被測程式源檔程式碼的總行數,分子為程式運行過程中經過的程式碼行數。例如,示例程式總共有 10 行程式碼,其中第 1 行和第 10 行必定會被執行。當輸入 a = 2, b = 3, c = 2
時,第 2、3、6、7 行會被執行到,所以陳述覆蓋率為 60%。因為第 4 行和第 8 行是互斥的,所以僅靠一個組輸入是無法達到 100% 陳述覆蓋的,可以構造 2 組輸入:a = 2, b = 3, c = 4
和 a = 2, b = 3, c = 1
,依靠這兩組輸入可以實現 100% 陳述覆蓋。
分支覆蓋(判定覆蓋)#
分支覆蓋(branch coverage)也叫判定覆蓋,要求每個帶有條件判定的陳述的各個分支都需要被覆蓋到。如果將程式碼的邏輯表示成一張流程圖的話,分支覆蓋也可以直觀地理解為圖中的所有連線和箭頭都要被覆蓋到:
如果將必定會連續執行的程式碼合併成一個節點,那麼這張圖就可以稍作簡化為:
雖說是要所有的連線和箭頭都覆蓋到,但一般只考慮條件陳述和迴圈陳述所產生的分支。例如對於第 2 行的條件陳述,需要覆蓋 2->3
和 2->6
這兩個分支;對於第 4 行的條件陳述,需要覆蓋 4->5
和 4->6
這兩個分支。
示例程式中總共有 4 個條件陳述,可以取 4 個真值。對於一組輸入 a = 2, b = 3, c = 2
,它運行後在各個條件陳述產生的判定值為 T, F, T, F
,分別對應分支 2->3, 3->6, 6->7, 7->10
, 要讓分支覆蓋率達到 100%,只要創造一組輸入,使得判定值取 F, T, F, T
即可,但是在示例程式中,第一個條件取 F 是無法讓第二個條件取 T 的。下面這 3 組輸入則可以讓分支覆蓋率達到 100%:
a = 2, b = 3, c = 2 => T, F, T, F
a = 10, b = 3, c = 4 => T, T, F, _
a = 1, b = 1, c = 1 => F, _, T, T
每個判定都取到了 T 和 F,滿足了分支覆蓋的要求。
條件覆蓋#
一個判定往往會由多個條件(一個能夠產生布林值的最小的表達式)經過與、或、非等布林運算組合而成,因此以更加細粒度的視角去衡量控制流的變化,可以用條件的取值取代上述分支覆蓋中的判定概念,構成條件覆蓋度量。在示例程式中,對於第 2 行程式碼,需要考慮的不再是 a > 1 and b > 2
取 T、F 的情況,而是 a > 1
和 b > 2
分別取 T、F 的情況。在條件覆蓋中,只需要每個條件都取過 T 和 F 即可,例如一個輸入產生 T、T,另一個輸入產生 F、F,即達到了條件覆蓋 100%。在分支覆蓋中給出的 3 組輸入:
a = 2, b = 3, c = 2 => T, F, T, F
a = 10, b = 3, c = 4 => T, T, F, _
a = 1, b = 1, c = 1 => F, _, T, T
缺少了 a < 1
的情況,因此只要把第三組輸入替換為 a = 0, b = 1, c = 1
。
路徑覆蓋#
路徑覆蓋是最細粒度的覆蓋,他將程式從入口(一般是 main 函數)到出口(exit、panic、main 函數中的 return 陳述)的每一條陳述組合成路徑,只要有一條陳述不同,就視為不同的路徑。示例程式存在的路徑為(希望我沒有疏漏):
1,2->3,4->5->6->7,8->9->10
1,2->3,4->5->6->7,8->10
1,2->3,4->5->6->10
1,2->3,4->6->7,8->9->10
1,2->3,4->6->7,8->10
1,2->3,4->6->10
1,2->6->7,8->9->10
1,2->6->7,8->10
1,2->6->10
需要注意的是,這只是從程式碼中分析得到的所有可能的路徑,實際情況下有些路徑是不成立的,例如 3,4->5
和 7,8->9
不可能出現在同一條路徑中,因此,對於示例程式,100% 路徑覆蓋是不可能的。
路徑覆蓋是這幾種覆蓋度量中要求最高的,只要滿足了路徑覆蓋,就必然能夠滿足其他所有覆蓋(實際不存在的情況除外)。但是在實際生活中的程式不僅包含數不清的條件陳述,還有迴圈陳述,以及多個條件和迴圈陳述的組合,產生的路徑數量會隨著條件、迴圈陳述的個數的增長呈現出指數級的上升,要實現較高的路徑覆蓋率是極為困難的。給定一個程式的程式碼,要計算程式中存在的路徑數量也是幾乎不可能的,因此需要計算路徑覆蓋時往往採用計數而不是計算比率的方式,能夠觸發更多不同路徑的輸入被視作更高品質的測試用例。