Chapter 22 Developer Testing
開發人員測試
測試是最常見的改善品質的活動——這種實踐得到許多業界和學界研究,以及商業經驗的支援。
·單元測試(Unit testing)是將一個程式員或者Team Dev所編寫的,一個完整的類,子程式或者小程式
,從完整的的系統中隔離出來進行測試。
·組件測試(Component testing)是將一個類,包,小程式或者其他程式元素,從一個更加完整的系
統中隔離出來進行測試,這些測試代碼涉及到多個程式員或者多個團隊。
·整合測試(Integration testing)是對兩個或者更多的類,包,組件或者子系統進行聯合測試,這些
組件由多個程式員或者Team Dev所建立。這種測試通常有了2個可以進行測試的類的時候就應該儘快開始
,並且一直持續到整個系統的開發完成。
·迴歸測試(Regression Testing)是指重複執行以前的測試案例,以便在原先通過了相同的測試集合
的軟體中尋找缺陷。
·系統測試(System testing)是在最終的配置下(包括同其他軟硬體系統的整合)運行整個軟體。以
便測試安全,效能,資源消耗,時序方面的問題,以及其他無法在低級整合上測試的問題。
開發人員進行的測試,通常包括單元測試,組件測試和整合測試,但有時候還會包括迴歸測試和系統測試
。
測試通常分為2大類:黑箱測試和白盒測試(或者玻璃盒測試)。
有些程式員會將術語“測試testing”和“調試debugging”混用,但是嚴謹的程式員會區分這兩種活動。測
試是一種檢查錯誤的方法,而調試意味著錯誤已經被發現了,要做的是診斷錯誤消滅造成這些錯誤的根
本原因。
22.1 Role of Developer Testing in Software Quality
開發人員測試在軟體品質中的角色
測試對於絕大多數開發人員來說都是一種煎熬。
你必須期望在你的代碼裡由錯誤。儘管這種期望似乎有悖常理,但是你應該期望找到這個錯誤的人是你
,而不是別人。
怎樣利用開發人員測試的結果?最直接地,你可以用這個結果來評估正在開發的產品的可靠性。
測試結果的另一個用途是,他可以用於指導對軟體的修正,並且通常也是這樣。
最後,測試發現缺陷的記錄有助於你歸納出程式中最常見錯誤的類型。你可以用這個資訊去選擇適當的
培訓課程,指引今後的技術複查活動,設計未來的測試案例。
·Testing During Contruction 構建中測試
構建期間,通常你會寫一個子程式或者類,現在頭腦中檢查它,然後對它進行複查或者測試。
無論你的整合測試或者系統測試策略如何,在將一個部分同其他部分組合之前,你都需要對它進行徹底
的單元測試。
如果講幾個沒有經過測試的子程式放到一起,結構發現了一個錯誤,那麼這幾個子程式都有嫌疑。假如
每次只將一個子程式加入到此前經過測試的子程式集合中,那麼一旦發現了錯誤,你就會知道這是新子
程式或者其介面所引發的問題,調試就會輕鬆很多。
22.2 Recommended Approach to Developer Testing 開發人員測試的推薦方法
採用系統化的開發人員測試方法,能最大限度提高你發現各種錯誤的能力,同時讓你的花費也最少。
·對每一項相關的需求進行測試,以確保需求都已經被實現。
在需求階段就計劃好這一部分的測試案例,或者至少儘早開始——最好在你開始編寫待測試
的單元之前。
安全層級,資料存放區,安裝過程以及系統可靠性等,常常在需求階段被忽略。
·對每一個相關的設計關注點進行測試。
·用基礎測試(basis testing)來擴充針對需求和設計的詳細測試案例。
·使用一個檢查表(check list),其中記錄著你在本項目迄今所犯的,以及在過去的項目所犯的錯誤類
型。
·Test First or Test Last 測試先行還是測試後行
1. 在開始寫代碼之前先寫測試案例,並不比之後寫要多花工夫。只是調整了編寫活動的順序而已。
2. 假如你先編寫測試案例,那麼你將可以更早發現缺陷,同時也更容易修正它們。
3. 先編寫測試案例,將迫使你在開始寫代碼之前至少思考一下需求和設計。
4. 先寫,能更早地把需求上的問題暴露出來。因為對一個糟糕的需求來說,要寫出測試案例是一件困難
的事情。
5. 如果你儲存了最初編寫的測試案例——這是你應該做的, 那麼先進行測試並非唯一的選擇,你仍然可
以最後在進行測試。
總而言之,測試先行的編程是過去十年中所形成的最有用的軟體開發實踐之一。同時也是一
個非常好的通用方法。
·Limitations of Developer Testing 開發人員測試的局限性
1. 開發人員測試傾向於“乾淨測試”。
開發人員往往做一些檢驗代碼能否工作的測試(乾淨測試,clean tests),而不是做所有可
能讓代碼失效的測試(骯髒測試,dirty tests)。
在不成熟的測試機構裡,骯髒測試同乾淨測試的數量比是1:5。成熟的測試機構傾向於讓肮
髒測試的數量是乾淨測試的5倍。
2. 開發人員測試對覆蓋率有過於樂觀的估計。
3. 開發人員測試往往會忽略一些更複雜的測試覆蓋率類型。
大多數開發人員看到的測試覆蓋率應該稱作“100%的語句覆蓋率”。這隻是一個好的開始,但
這還遠遠不夠。更好的覆蓋率標準是所謂的“100%分支覆蓋率”,也就是對每一個判斷語句至少測試一個
真值和一個假值。
22.3 Bag of Testing Tricks 測試技巧錦囊
1. Incomplete Testing 不完整測試
由於進行完全測試實際上是不可能的,因此測試的竅門就在於選擇那些最有可能找到錯誤的
測試案例。當你規劃測試的時候,要去除那些不會告訴你任何新情況的測試案例。
2. Structure Basis Testing 結構化的基礎測試
所需基礎測試案例的最少數量可以用以下簡單方法計算:
a. 對通過子程式的直路,開始的時候記1.
b. 遇到下面的每個關鍵字或者其等價物時,加1.
if, while, repeat, for, and 以及 or。
c. 遇到每一個case語句就加1,如果case語句沒有預設的情況,則再加1.
這樣的出的數字,意思是至少需要6個測試案例。
2. Data-Flow Testing 資料流測試
資料流測試基於如下觀念:資料使用的出錯幾率至少不亞於控制流程。
Boris Beizer生稱,全部代碼中至少有一半是資料聲明和初始化。(1990)
資料的狀態可以是以下3中狀態之一:
a 已定義(defined)
b 已使用(used)
c 已銷毀 (killed)
除了這3個之外,為了方便起見,還有一些術語來描述對變數進行某種操作之前或之後,控制流程進入或退
出某個子程式的狀態。
d 已進入(entered)
e 已退出(exited)
當工作毫無進展的時候,就應該列出所有的已定義-已使用的組合。這樣做看起來工作量很大,但他保證
你能夠獲得用基礎測試方法並不能輕鬆發現的用例。
·Equivalent Partitioning 等價類別劃分
一個好的測試案例應該覆蓋可輸入資料中的很大一部分。如果兩個用例能揭示錯誤完全相同,那麼只
要一個就夠了。“等價類別劃分”的概念是這一想法的嚴格表達形式,應用它有助於減少所需用例的數量。
·Error Guessing 猜測錯誤
“猜測錯誤”這一措辭變現了對這一睿智概念的淺薄理解。它的真正含義應該是在猜測程式會在哪裡出錯的
基礎上建立測試案例,儘管這也意味著猜測中會有一些牽強附會的成分。
如果你保留了一份記錄過去所犯錯誤種類的列表,那麼你就能提高“猜測錯誤”的命中率。
·Boundary Analysis 邊界值分析
運用邊界值條件進行測試最豐碩的戰果之一就是off-by-one錯誤。這種錯誤即當你想用num時,寫成了
num-1;當你想用“>”的時候寫成了“>=”,這些都是常見的失誤。
邊界值分析的思想就是寫一些測試案例來測試邊界值條件。
·Compound Boundaries 複合邊界值
有一種邊界值更加隱蔽,就是當邊界條件涉及到互相關聯的多個變數的時候。例如,2個變數相乘,他們
的值都是大的正數,負數,零。 如果傳入的字串都長得很不尋
常呢?
·Use Test Cases That Make Hand-Checks Convenient 採用容易手工檢查的測試案例
22.4 Typical Errors 經典錯誤
·Which Classes Contain the Most Errors 哪些類包含最多的錯誤
絕大多數錯誤往往與少數幾個具有嚴重缺陷的子程式有關。下面是錯誤和代碼之間的普遍關係。
1. 80%的錯誤存在於項目20%的類或者子程式中。
2. 50%的錯誤被發現於項目5%的類當中。
如果你認為這些關係無關緊要,很可能是因為你對下面幾個結論一無所知。
首先,項目中20%的子程式佔用了80%的開發成本。
其次,無論高缺陷率子程式在成本中所佔的具體比例如何,這些子程式的成本的確是異常高昂的。
再次,子程式開發成本昂貴帶來的影響也顯而易見。隨話說,“時間就是金錢”,推論是“金錢就是時間”,
也就是說,如果能避免捲入那些煩人的子程式中,那麼你就可以省下近80%的成本,從而節約一大段開
發時間。這清晰的描述了軟體品質的普遍原則:提高品質就能縮短開發週期,同時降低開發成本。
最後,避免維護惹人厭煩的子程式同樣具有明顯的重要意義。維護工作應該圍繞如何確定容易出問題的
子程式,如何把這些部分推到重來,重新設計並編寫代碼。
·Errors By Classification 錯誤的分類
Boris Beizer將多個研究的資料綜合起來,得到一種非常詳盡的錯誤分類方法。下面是它的研究結果總
結:
25.18% 結構方面的問題
22.44% 資料
16.19% 已實現的功能
9.88% 構建
8.98% 整合
8.12% 功能需求
2.76% 測試的定義或者執行
1.74% 整個系統,軟體架構
4.71% 未歸類
下面是資料給我們的啟示,提示:
1. 大多數錯誤的影響範圍是相當有限的。
85%的錯誤可以在修改不超過一個子程式的範圍得以修正。
2. 許多錯誤發生在構建的範疇之外。
3中最為常見錯誤的源頭,他們是:缺乏應用領域的知識,頻繁變動且相互矛盾的需求, 以
及溝通和協調的失誤。
3. 大多數的構建期錯誤是編程人員的失誤造成的。95%以上。
4. 讓人驚奇的是,筆誤(拼字錯誤)是一個常見的問題根源。
一項研究發現,現在構建階段產生的錯誤中,有36%是拼字錯誤。如果你對此有所懷疑的話,想一想3
個有史以來最昂貴的軟體錯誤——其代價分別是16億美元,9億美元和2.45億美元,都是因為原本正確
的程式中的一個不正確的字元造成的(Weinberg 1983)。
5. 研究程式員所犯錯誤原因時,錯誤理解設計這條會經常出現
6. 大多數錯誤會很容易修正
7. 總結所在組織中對付錯誤的經驗
·Proportion of Errors Resulting from Faulty Construction
不完善的構建過程引發錯誤所佔的比例
隨著項目的規模的增長,在構建期間產生的錯誤所佔的比例會下降,然而即使是在巨型項目裡面,構建
錯誤也會佔全部錯誤的45%至75%。
·How Many Errors Should you Expect to Find 你期望能發現多少錯誤
1. 業界的經驗是,在已發行的軟體中平均1000行發現1~25個錯誤。
2. 微軟應用程式部門的經驗是,自我裝載程式大約每1000行代碼有10~20個缺陷。而對於發行產品
則大約是1000行代碼0.5個缺陷。
3. Harlan Mills所倡導的“淨室開發”的技術,可以獲得低至1000行代碼3個缺陷(自我裝載階段),以
及1000行0.1個缺陷(產品發布階段)的錯誤率。 只有少數幾個項目,例如太空梭的軟體,能夠達到
每50萬行代碼0缺陷的水平。這需要使用一個系統的形式化開發方式,同時複查(peer review),以及
統計測試。
4. Watts Humphrey報告稱,使用“團隊軟體開發過程”(Team Software Process, TSP)的開發小
組,可以達到大約1000行0.06個缺陷的水平。TSP把訓練開發人員如何避免缺陷放在了第一位。
TSP和淨室開發項目的結論從另一個角度證明了軟體品質的普遍原則:開發高品質的軟體,比開發低
品質的軟體然後修正的成本要低廉。 一個使用淨室開發技術,經過全面檢驗合格並擁有8萬行代碼的項
目,其生產效率相當於平均每個工作月740行代碼。而開發經過全面檢驗合格的代碼的業界平均效率大約
是平均沒工作月250行~300行代碼,這包含了所有非代碼的日常開銷。
這裡我們看到的成本的節約和生產效率提升,其源泉在於使用TSP或者淨室技術的項目幾乎沒有將時
間投入到調試當中。不在調試上面花時間?那真是一個很有價值的目標!
·Errors in Testing Itself 測試本身的錯誤
通過下列幾項工作減少測試案例當中的錯誤量:
1. 檢查你的工作
2. 開發軟體的時候就要計劃好測試案例
3. 保留你的測試案例
4. 將單元測試納入測試架構
22.5 Test-Support Tools 測試支援工具
· Building Scaffolding to Test Individual Classes 為測試各個類構造 腳手架
·Diff Tools Diff工具
·Test-Data Generators 測試資料產生器
·Converage Monitors 覆蓋率監視器
·Data Recorder/Logging 資料記錄器/日誌記錄器
·Symbolic Debuggers 符號調試器
·System Perturbers 系統幹擾器
一類測試支援工具是用來對系統進行幹擾的。這類測試支援工具如下多種功能。
1. 記憶體填充
2. 記憶體抖動
3. 選擇性記憶體失敗
4. 記憶體訪問性檢查(邊界檢查)
·Error Databases 錯誤資料庫
存放以往錯誤的資料庫是另一種強大的測試載入器,這樣一個資料庫既是管理工具,也是技術工具。它
讓你能檢查重複出現的錯誤,即時擷取已糾正錯誤和已發現錯誤之比率,以及跟蹤錯誤的處理狀態和嚴
重層級。
22.6 Improving Your Testing 改善測試過程
1. Planning To Test 有計劃的測試
2. Retesting (Regression Testing)重新測試(迴歸測試)
這次的修改沒有給產品引入任何新的錯誤。為確保軟體沒有倒退,或者沒有“迴歸”而設計的測
試,被稱為“迴歸測試”。
3. Automated Testing 自動化測試
管理迴歸測試唯一可行的方法,就是將其變成一個自動化的過程。
自動化測試的好處:
a 自動化測試發生錯誤的幾率比手動測試要小。
b 一旦你把一個測試自動化了,那麼你只需少下功夫,就很容易在項目的剩餘部分繼續實施
自動化。
c 如果測試是自動進行的,那麼就可以頻繁地運行,看看新的check in的代碼是否破壞了原
有的程式。例如daily build,煙霧測試 (Smoke Test)以及極限編程等等。
d 自動化測試可以提高問題剛產生就被發現的可能性,這可能顯著減少分析和修正錯誤所需
要的工作量。
e 由於自動化測試能夠提升快速發現修改所引入錯誤的幾率,因此它為大規模代碼修改提供
了一張安全網。
f 自動化測試在那些新的,不穩定的技術環境當中特別有價值,因為它提早稀釋了環境改變對
系統的影響,而非事後補救。
22.7 Keeping Test Records 保留測試記錄
Key Points 要點
1. 開發人員測試是完整測試策略的一個關鍵區段。
2. 同編碼之後編寫測試案例相比較,編碼開始之前編寫測試案例,工作量和花費的時間差不多。但是後
者可以縮短缺陷-偵測-調試-修正這一周期。
3. 即使考慮到了各種可用的測試手段,測試仍然只是良好軟體品質計劃的一部分。高品質的開發方法至
少和測試一樣重要,這包括儘可能減少需求和設計階段的缺陷。在檢測錯誤方面,協同開發的成效至少
與測試相當。這些方法檢測到錯誤的類型也不同。
4. 你可以根據各種不同的思路來產生很多測試案例,這些思路包括基礎測試,資料流分析,邊界分析,
錯誤資料類型以及正確的資料類型等等。你還可以通過猜測錯誤的方式得到更多的測試案例。
5. 錯誤往往集中在少數幾個容易出錯的類和子程式上。找出這部分代碼,重新設計和編寫他們。
6. 測試資料本身出錯的密度往往比測試代碼要高。尋找這種錯誤完全是浪費時間,又不能對代碼有所改
善,因此測試資料裡面的錯誤更加讓人煩惱。要想寫代碼一樣小心地開發測試案例,這樣才能避免產生
這種問題。
7. 自動化測試總體來說是很有用的,也是進行迴歸測試的基礎。
8. 從長遠來看,改善測試過程的最好辦法就是將其正常化,並對其進行評估,然後用從評估中獲得的經
驗教訓來改善這個過程。