讀古今文學網 > C語言解惑 > 13.9 程序的測試與調試 >

13.9 程序的測試與調試

在設計測試數據的時候,應當牢記,測試的目標是披露錯誤。如果用來尋找錯誤的測試數據找不到錯誤,我們就可以有信心相信程序的正確性。目標是採用那些能夠以盡量少的測試數據來發現盡量多的錯誤的測試數據。為了弄清楚對於一個給定的測試數據,程序是否存在錯誤,首先必須知道對於該測試數據,程序的正確結果應是什麼。

上節的二次方程求解的例子中,可以用如下兩種方法來判斷給定任意測試數據時程序的正確輸出是什麼。第1種方法是,計算出所測試二次方程的根。例如係數(a,b,c)=(1,-5,6)的二次方程的根為2和3,用測試數據(1,-5,6)對程序進行測試,即用程序所輸出的根與2和3進行比較,以驗證程序的正確性。第2種可行的方法用測試數據(1,-5,6)對程序進行測試,即是把程序所產生的根代入二次函數以驗證函數的值是否真為0,如果程序輸出的是2和3,可以計算出f(2)=2 2-5*2+6=0,f(3)=3 2-5*3+6=0。可以把以上驗證方法用計算機程序來實現。對於第1種方法,編寫測試程序輸入測試組(a,b,c)和期望的根,然後把程序計算出的根與期望的根進行比較;對於第2種方法,可以編寫代碼來計算被測試程序輸出的根相應的二次函數的函數值,然後驗證這個值是否為0。

一般主要從兩個方面來選擇測試數據:一是這個數據能夠發現錯誤的程度,二是能驗證採用這個數據時程序的正確性。

設計測試數據的技術有3種,白盒測試法、黑盒測試法和灰盒測試法。第3種方法是前兩種的混合。不同的測試在選擇測試用例方面有著很大差別。在黑盒法中,選擇測試用例考慮的是被測程序的功能,而不是實際的代碼。在白盒法中,選擇測試用例時是通過檢查被測試程序代碼來設計測試數據,以便使測試數據的執行結果能很好地覆蓋被測試程序的語句以及執行路徑。

1.黑盒法

最流行的黑盒法是I/O分類及因果圖,這裡僅探討I/O分類。在這種方法中,輸入數據和輸出數據空間被分成若干類,不同類中的數據會使程序所表現出的行為有質的不同,而相同類中的數據則使程序表現出本質上類似的行為。二次方程求解的例子中有3種本質上不同的行為:產生複數根,產生實數根且不同,產生實數根且相同。可以根據這3種行為把輸入空間分為3類。第1類中的數據將產生第1種行為;第2類中的數據將產生第2種行為;而第3類中的數據將產生第3種行為。一個測試集應至少從每一類中抽取一個輸入數據。

2.白盒法

白盒法基於對代碼的考察來設計測試數據。對一個測試集最起碼的要求就是使被測程序中的每一條語句都至少執行一次。這種要求被稱為語句覆蓋(statement coverage)。對於二次方程求解的例子,測試集{(1,-5,6),(1,-8,16),(1,2,5)}將使程序中的每一條語句都得以執行,而測試集{(1,-5,6),(1,3,2),(2,5,2)}則不能提供語句覆蓋。

在分支覆蓋中要求測試集要能夠使程序中的每一個條件都分別能出現true和false兩種情況。二次方程求解的FindRoots程序中的代碼有兩個條件:(d>0)和(d==0)。在進行分支覆蓋測試時要求測試集至少能使條件(d>0)和(d==0)分別出現一次為true、一次為false的情況。下面再以尋找數組中的最大元素所在位置的程序為例,說明白盒法中對測試數據的要求。


int Max
(int a[ ]
,int  n 
)
{//
尋找a[n]
中的最大元素
   int pos=0
;
   for 
(int i=1
;i<n
;i++
)
        if 
(a[pos]<a[i]
)
              pos=i
;
   return pos
;
}
  

程序返回數組a[n]中最大元素所在的位置。它依次掃瞄a[0]到a[n-1],並用變量pos來保存到目前為止所能找到的最大元素的位置。數據集{2,4,6,8,9}能夠提供語句覆蓋,但不能提供分支覆蓋,因為條件a[pos]<a[i]不會變成false。數據集{4,2,6,8,9}既能提供語句覆蓋也能提供分支覆蓋。

可以進一步加強分支覆蓋的條件,要求每個條件中的每個從句既能出現true也能出現false的情況,這種加強的條件被稱為從句覆蓋。一個從句在形式上被定義成一個不包含布爾操作符(如&&、||、!)的布爾表達式。表達式x>y,x+y<y*z以及c(c是一個布爾類型)都是從句的例子。考察如下語句:


if
(C1 && C2
)||
(C3&& C4
))   S1
;
else   S2
;
  

其中C1,C2,C3和C4是從句,S1和S2是語句。在分支覆蓋方式下,需要使用一個能使


((C1 && C2
)||
(C3 && C4
))
 

為true的測試數據以及一個能使該條件為false的測試數據。而從句覆蓋則要求測試數據能使4個從句C1,C2,C3和C4都分別至少取一次true值和至少取一次false值。

還可以繼續加強從句覆蓋要求使用16個測試數據集:每一個測試集對應4個從句值組合的情形。不過,其中有些組合是不可能的。

如果按照某個測試數據集來排列程序語句的執行次序,可以得到一條執行路徑。不同的測試數據可能會得到不同的執行路徑。如例13.14中解二次方程的程序僅存在3條執行路徑,第1行至第6行,第1、2、7~8行,第1、2、7、9~13行。而尋找數組中的最大元素所在位置程序的執行路徑則隨著n的增加而增加。執行路徑覆蓋要求測試數據集能使每條執行路徑都得以執行。對於二次方程求解程序,語句覆蓋、分支覆蓋、從句覆蓋以及執行路徑覆蓋都是等價的,但對於尋找數組中的最大元素所在位置程序,語句覆蓋、分支覆蓋、和執行路徑覆蓋三者是不同的,而分支覆蓋和從句覆蓋是等價的。

在上述這些白盒測試方法中,一般要求實現執行路徑覆蓋。一個能實現全部執行路徑覆蓋的測試數據同樣能實現語句覆蓋和分支覆蓋,然而,它可能無法實現從句覆蓋。全部執行路徑覆蓋通常會需要無數的測試數據或至少是非常可觀的測試數據,所以在實踐中一般不可能進行全部執行路徑覆蓋。

但是,所使用的測試數據應至少提供語句覆蓋。此外,必須測試那些可能會使程序出錯的特定情形。例如,對於一個用來對n≥0個元素進行排序的程序,除了測試n的正常取值外,還必須測試n=0、1這兩種特殊情形;如果該程序使用數組a[100],而n=0和99分別表示邊界條件,那麼還需要測試n=0和99這兩種情況。

3.灰盒法

灰盒測試方法是前兩種的混合。可以根據情況讓兩種方法相互配合,以便充分發揮黑盒法和白盒法各自的優點。

4.調試程序

測試能夠發現程序中的錯誤。一旦測試過程中產生的結果與所期望的結果不同,就可以瞭解到程序中存在錯誤。確定並糾正程序錯誤的過程被稱為調試(debug)。儘管透徹地形容程序調試的方法超出了本書的範圍,但這裡還是提供一些好的建議給大家:

(1)可以用邏輯推理的方法來確定錯誤語句。如果這種方法失敗,還可以進行程序跟蹤,以確定程序什麼時候開始出現錯誤。如果對於給定的測試數據程序需要運行很多指令,因而需要跟蹤太多語句,很難人工確定錯誤,此時,這種方法就不太可行了,在這種情況下,必須試著把可疑的代碼分離出來,專門跟蹤這段代碼。

(2)不要試圖通過產生異常來糾正錯誤。異常的數量可能會迅速增長。必須首先找到需要糾正的錯誤,然後根據需要重新設計。

(3)在糾正一個錯誤時,必須保證不會產生一個新的、以前沒有的錯誤。要用原本能使程序正確運行的測試數據來運行糾正過錯誤的程序,確信對於該數據,程序仍然正確。

(4)在測試和調試一個有錯的程序時,應從一個與其他函數獨立的函數開始。這個函數應該是一個典型的輸入或輸出函數,然後每次引入一個尚未測試的函數,測試並調試更大一些的程序。這種策略被稱為增量測試與調試(incremental test and debug)。在使用這種策略時,可以有理由認為產生錯誤的語句位於剛剛引入的函數之中。