軟體工程系列文章三
系統測試的意義、目的及原則
系統測試是為了發現錯誤而執行程式的過程,成功的測試是發現了至今尚未發現的錯誤的測試。
測試的目的就是希望能以最少的人力和時間發現潛在的各種錯誤和缺陷。用戶應根據開發各階段的需求、設計等文件或程式的內部結構精心設計測試案例(Test Case),並利用這些實例來執行程式,以便發現錯誤的過程。
資訊系統測試應包括軟體測試、硬體測試和網路測試。硬體測試、網路測試可以根據具體的效能指標進行,此處所說的測試更多的是指軟體測試。
系統測試是保證系統品質和可靠性的關鍵步驟,是對系統開發過程中的系統分析、系統設計和實作的最後複查。根據測試的概念和目的,在進行資訊系統測試時應遵循以下基本原則:
- 應儘早並不斷地進行測試。測試不是在應用系統開發完之後才進行的。由於原始問題的複雜性、開發各階段的多樣性以及參加人員之間的協調等因素,使得在開發的各個階段都有可能出現錯誤。因此,測試應貫穿在開發的各個階段,應儘早糾正錯誤,消除隱患。
- 測試工作應該避免由原開發軟體的人或小組承擔。一方面,開發人員往往不願否定自己的工作,總認為自己開發的軟體沒有錯誤;另一方面,開發人員的錯誤很難由本人測試出來,很容易根據自己撰寫程式的思路來制定測試思路,具有侷限性。測試工作應由專門人員來進行,這樣會更客觀、更有效。
- 在設計測試方案時,不僅要確定輸入資料,而且要根據系統功能確定預期輸出結果。將實際輸出結果與預期結果相比較就能發現測試對象是否正確。
- 在設計測試案例時,不僅要設計有效、合理的輸入條件,也要包含不合理、失效的輸入條件。在測試的時候,人們往往習慣按照合理的、正確的情況進行測試,而忽略了對異常、不合理、意想不到的情況進行測試,而這可能就是隱患。
- 在測試程式時,不僅要檢驗程式是否做了該做的事,還要檢驗程式是否做了不該做的事。多餘的工作會帶來副作用,影響程式的效率,有時會帶來潛在的危害或錯誤。
- 嚴格按照測試計畫來進行,避免測試的隨意性。測試計畫應包括測試內容、進度安排、人員安排、測試環境、測試工具和測試資料等。嚴格地按照測試計畫可以保證進度,使各方面都得以協調進行。
- 妥善保存測試計畫、測試案例,作為軟體文件的組成部分,為維護提供方便。
- 測試例子都是精心設計出來的,可以為重新測試或追加測試提供方便。當糾正錯誤、系統功能擴充後,都需要重新開始測試,而這些工作的重複性很高,可以利用以前的測試案例或在其基礎上修改,然後進行測試。
系統測試階段的測試目標來自於需求分析階段。
傳統軟體的測試策略
有效的軟體測試實際上分為 4 步進行,即單元測試、整合測試、確認測試和系統測試。
(一) 單元測試
單元測試也稱為模組測試,在模組編寫完成且無編譯錯誤後就可以進行。單元測試側重於模組中的內部處理邏輯和資料結構。如果選用機器測試,一般用白箱測試法。這類測試可以對多個模組同時進行。
(1) 單元測試的測試內容
單元測試主要檢查模組的以下 5 個特徵:
- 模組介面。模組的介面保證了測試模組的資料流可以正確地流入、流出。在測試中應檢查以下要點:
- 測試模組的輸入參數和形式參數在個數、屬性、單位上是否一致。
- 呼叫其他模組時,所給出的實際參數和被呼叫模組的形式參數在個數、屬性、單位上是否一致。
- 呼叫標準函數時,所用的參數在屬性、數目和順序上是否正確。
- 全域變數在各模組中的定義和用法是否一致。
- 輸入是否僅改變了形式參數。
- 開/關的語句是否正確。
- 規定的 I/O 格式是否與輸入/輸出語句一致。
- 在使用檔案之前是否已經打開檔案,或使用檔案之後是否已經關閉檔案。
- 區域資料結構。在單元測試中,區域資料結構出錯是比較常見的錯誤,在測試時應重點考慮以下因素:
- 變數的說明是否合適。
- 是否使用了尚未賦值或尚未初始化的變數。
- 變數的初始值或預設值是否正確。
- 變數名稱是否有錯 (例如拼寫錯誤)。
- 重要的執行路徑。在單元測試中,對路徑的測試是最基本的任務。由於不能進行窮舉測試,需要精心設計測試例子來發現是否有計算、比較或控制流等方面的錯誤。
- 計算方面的錯誤。算術運算的優先次序不正確或理解錯誤;精度不夠;運算對象的類型彼此不相容;演算法錯誤;運算式的符號表示不正確。
- 比較和控制流的錯誤。本應相等的量由於精度造成不相等;不同類型進行比較;邏輯運算子不正確或優先次序錯誤;迴圈終止不正確 (如多迴圈一次或少迴圈一次)、無窮迴圈;不恰當地修改迴圈變數;當遇到分支迴圈時出口錯誤等。
- 出錯處理。好的設計應該能預測到出錯的條件並且有對出錯處理的路徑。雖然電腦可以顯示出錯訊息的內容,但仍需要程式設計師對出錯進行處理,保證其邏輯的正確性,以便於用戶維護。
- 邊界條件。邊界條件的測試是單元測試的最後工作,也是非常重要的工作。軟體容易在邊界出現錯誤。
(2) 單元測試過程
由於模組不是獨立運行的程式,各模組之間存在呼叫與被呼叫的關係。在對每個模組進行測試時,需要開發兩種模組:
- 驅動模組 (Driver)。相當於一個主程式,接受測試例子的資料,將這些資料送到測試模組,輸出測試結果。
- 樁模組 (Stub,也稱為存根模組)。樁模組用來代替測試模組中所呼叫的子模組,其內部可進行少量的資料處理,目的是為了檢驗入口,輸出呼叫和返回的資訊。

提高模組的內聚度可以簡化單元測試。如果每個模組只完成一種功能,對於具體模組來講,所需的測試方案資料會顯著減少,而且更容易發現和預測模組中的錯誤。
(二) 整合測試
整合測試(Integration Testing)就是把模組按系統設計說明書的要求組合起來進行測試。即使所有的模組都通過了測試,在整合之後,仍然可能出現問題。
通常,整合測試有兩種方法:一種是非增量整合,分別測試各個模組,再把這些模組組合起來整體測試;另一種是增量整合,即以小增量的方式逐步進行構造和測試。
下面是一些增量整合策略:
(1) 由上而下整合測試 (Top-down Integration)
由上而下整合測試是一種構造軟體體系結構的增量方法。模型的整合順序為從主控模組 (主程式) 開始,沿著控制階層逐步向下,以深度優先或廣度優先的方式將從屬於 (或間接從屬於) 主控模組的模組整合到結構中。
深度優先整合是首先整合位於程式結構中主控路徑上的所有構件,也可以根據特定應用系統的特徵進行選擇。

- 主控模組用作測試驅動模組,用這些從屬於主控模組的所有模組代替樁模組。
- 依賴所選擇的整合方法 (即深度優先或廣度優先),每次用實際模組替換一個從屬樁模組。
- 在整合每個模組後都進行測試。
- 在完成每個測試集之後,用實際模組替換另一個樁模組。
- 可以執行迴歸測試,以確保沒有引入新的錯誤。
- 回到第 2 步繼續執行此過程,直到完成了整個程式結構的構造。
不用編寫驅動模組,需要編寫樁模組。
(2) 由下而上整合測試 (Bottom-up Integration)
由下而上整合測試就是從原子模組 (程式結構的最底層構件) 開始進行構造和測試。由於構件是由下而上整合的,在處理時所需要的從屬於給定階層的模組總是存在的,因此,沒有必要使用樁模組。由下而上整合策略可以利用以下步驟來實現:

- 連接底層構件以構成完成特定子功能的簇 (Cluster)。
- 編寫驅動模組 (測試的控制程式) 以協調測試案例的輸入和輸出。
- 測試簇。
- 去掉驅動程式,沿著程式結構向上逐步連接簇。
不需要編寫樁模組,需要編寫驅動模組。
(3) 迴歸測試 (Regression Testing)
每當加入一個新模組作為整合測試的一部分時,軟體發生變更,建立了新的資料流路徑,可能出現新的 I/O,以及呼叫新的控制邏輯。這些變更可能會使原來可以正常工作的功能產生問題。在整合測試策略的環境下,迴歸測試是重新執行已測試過的某些子集,以確保變更沒有傳播不期望的副作用。
迴歸測試有助於保證變更不引入無意識行為或額外的錯誤。迴歸測試可以手工進行,方法是重新執行所有測試案例的子集,或者利用捕捉/回放工具自動執行。捕捉/回放工具使軟體工程師能夠為後續的回放與比較捕捉測試案例和測試結果。迴歸測試要執行的測試子集包含以下 3 種測試案例:
- 能夠測試軟體所有功能的具有代表性的測試樣本。
- 額外測試,側重於可能會受變更影響的軟體功能。
- 側重於已發生變更的軟體構件測試。
隨著整合測試的進行,迴歸測試的數量可能變得相當龐大,因此,應將迴歸測試案例設計成只包括設計每個主要程式功能的一個或多個錯誤類別的測試。一旦發生變更,對每個軟體功能重新執行所有的測試是不切實際的,而且效率很低。
(4) 冒煙測試 (Smoke Testing)
當開發軟體產品時,冒煙測試是一種常見的整合測試方法,是時間關鍵專案的決定性機制,它讓軟體團隊頻繁地對專案進行評估。
測試方法
在軟體測試過程中,應該為定義軟體測試範本,即將特定的測試方法和測試案例設計放在一系列的測試步驟中。
軟體測試方法分為靜態測試和動態測試。
- 靜態測試。靜態測試是指被測試程式不在機器上運行,而是採用人工檢測和電腦輔助靜態分析的手段對程式進行檢測。
- 人工檢測。人工檢測不依靠電腦而是依靠人工審查程式或評審軟體,包括程式碼檢查、靜態結構分析和程式碼品質度量等。
- 電腦輔助靜態分析。利用靜態分析工具對被測試程式進行特性分析,從程式中提取一些資訊,以便檢查程式邏輯的各種缺陷和可疑的程式構造。
- 動態測試。動態測試是指通過執行程式發現錯誤。在對軟體產品進行動態測試時可以採用黑箱測試法和白箱測試法。
測試案例由測試輸入資料和與之對應的預期輸出結果組成。在設計測試案例時,應當包括合理的輸入條件和不合理的輸入條件。
(一) 黑箱測試 (Black-box Testing)
黑箱測試也稱為功能測試,在完全不考慮軟體的內部結構和特性的情況下,測試軟體的外部特性。
常見的黑箱測試技術有等價類別劃分、邊界值分析、錯誤推測和因果圖等。
(1) 等價類別劃分 (Equivalence Partitioning)
等價類別劃分將程式的輸入域劃分為若干等價類別,然後從每個等價類別中選取一個代表性資料作為測試案例。每一類的代表性資料在測試中的作用等價於這一類中的其他值,這樣就可以用少量代表性的測試案例取得較好的測試效果。等價類型劃分有兩種不同的情況:有效等價類別和無效等價類別。在設計測試案例時,要同時考慮這兩種等價類別。
(2) 邊界值分析 (Boundary Value Analysis)
輸入的邊界比中間更加容易發生錯誤,因此用邊界值分析來補充等價類別劃分的測試案例設計技術。邊界值劃分選擇等價類別邊界的測試案例,既注重於輸入條件邊界,又適用於輸出域測試案例。
(3) 錯誤推測 (Error Guessing)
錯誤推測是基於經驗和直覺推測程式中所有可能存在的各種錯誤,從而有針對性地設計測試案例的方法。其基本思想是列舉出程式中所有可能有的錯誤和容易發生錯誤的特殊情況,根據它們選擇測試案例。
(4) 因果圖 (Cause-Effect Graphing)
因果圖法是從自然語言描述的程式規格說明中找出因 (輸入條件) 和果 (輸出或程式狀態的改變),通過因果圖轉換為判定表。
(二) 白箱測試 (White-box Testing)
白箱測試也稱為結構測試,根據程式的內部結構和邏輯來設計測試案例,對程式的路徑和過程進行測試,檢查是否滿足設計的需要。
白箱測試常用的技術是邏輯涵蓋、迴圈涵蓋和基本路徑測試。
白箱測試的原則如下:
- 程式模組中的所有獨立路徑至少執行一次。
- 在所有的邏輯判定中,取「真」和取「假」的兩種情況至少都能執行一次。
- 每個迴圈都應在邊界條件和一般條件下各執行一次。
- 測試程式內部資料結構的有效性等。
(1) 邏輯涵蓋 (Logic Coverage)
邏輯涵蓋考察用測試資料執行被測程式時對程式邏輯的涵蓋程度,主要的邏輯涵蓋標準有指令涵蓋、判定涵蓋、條件涵蓋、判定/條件涵蓋、條件組合涵蓋和路徑涵蓋 6 種。
- 指令涵蓋 (Statement Coverage)。指令涵蓋是指選擇足夠的測試資料,使被測試程式中的每條指令至少執行一次。指令涵蓋對程式執行邏輯的涵蓋很低,因此一般認為它是很弱的邏輯涵蓋。
- 判定涵蓋 (Decision Coverage)。判定涵蓋是指設計足夠的測試案例,使得被測程式中的每個判定運算式至少獲得一次「真」值和「假」值,或者說是程式中的每一個取「真」分支和取「假」分支至少都通過一次,因此判定涵蓋也稱為分支涵蓋。判定涵蓋要比指令涵蓋更強一些。
- 條件涵蓋 (Condition Coverage)。條件涵蓋是指構造一組測試案例,使得每一判定語句中每個邏輯條件的各種可能的值至少滿足一次。
- 判定/條件涵蓋 (Decision/Condition Coverage)。判定/條件涵蓋是指設計足夠的測試案例,使得判定中每個條件的所有可能取值 (真/假) 至少出現一次,並使每個判定本身的判定結果 (真/假) 也至少出現一次。
- 條件組合涵蓋 (Condition Combination Coverage)。條件組合涵蓋是指設計足夠的測試案例,使得每個判定中條件的各種可能值的組合都至少出現一次。滿足條件組合涵蓋的測試案例是一定滿足判定涵蓋、條件涵蓋和判定/條件涵蓋的。
- 路徑涵蓋 (Path Coverage)。路徑涵蓋是指涵蓋被測試程式中所有可能的路徑。
(2) 迴圈涵蓋
執行足夠的測試案例,使得迴圈中的每個條件都得到驗證。
(3) 基本路徑測試
基本路徑測試法是在程式控制流圖的基礎上通過分析控制流圖的環路複雜性,導出基本可執行路徑集合,從而設計測試案例。
除錯 (Debugging)
除錯發生在測試之後,其任務是根據測試時所發現的錯誤找出原因和具體的位置,進行改正。
常用的除錯方法有以下幾種:
(1) 試探法
除錯人員分析錯誤的症狀,猜測問題所在的位置,利用在程式中設置輸出語句,分析暫存器、記憶體的內容等手段獲得錯誤的線索,一步步地試探和分析錯誤的所在。
(2) 回溯法
除錯人員從發現錯誤症狀的位置開始,人工沿著程式的控制流程往回追蹤程式碼,直到找出錯誤根源為止。
(3) 對分搜尋法 (Binary Search Method)
這種方法主要用來縮小錯誤的範圍,如果已經知道程式中的變數在若干位置的正確取值,可以在這些位置上給這些變數以正確值,觀察程式執行的輸出結果,如果沒有發現問題,則說明從賦予變數一個正確值開始到輸出結果之間的程式沒有錯誤,問題可能在除此之外的程式中。否則錯誤就在所考察的這部分程式中,對含有錯誤的程式段再使用這種方法,直到把故障範圍縮小到比較容易診斷為止。
(4) 歸納法
歸納法就是從測試所暴露的問題出發,收集所有正確或不正確的資料,分析它們之間的關係,提出假想的錯誤原因,用這些資料來證明或反駁,從而查出錯誤所在。
(5) 演繹法
演繹法根據測試結果,列出所有可能的錯誤原因;分析已有的資料,排除不可能和彼此矛盾的原因;對其餘的原因,選擇可能性最大的,利用已有的資料完善該假設,使假設更具體;用假設來解釋所有的原始測試結果,如果能解釋這一切,則假設得以證實,也就找出錯誤。