提高軟件質(zhì)量:如何處理數(shù)據(jù)發(fā)現(xiàn)更多 Bug
歷史累積的測(cè)試套件既測(cè)試得太多也測(cè)試得太少
軟件系統(tǒng)的特性通常隨著版本的發(fā)布不斷增長(zhǎng),它們的測(cè)試套件也是如此。這會(huì)導(dǎo)致測(cè)試執(zhí)行時(shí)間變得越來(lái)越長(zhǎng)。對(duì)于手動(dòng)測(cè)試來(lái)說(shuō),這意味著測(cè)試人員要付出更多的努力,從而直接導(dǎo)致更高的成本。對(duì)于自動(dòng)化測(cè)試,這意味著開發(fā)人員在得到測(cè)試結(jié)果之前需要等待更長(zhǎng)的時(shí)間。我們看到許多自動(dòng)化測(cè)試套件的執(zhí)行時(shí)間從幾分鐘到幾小時(shí)增加到幾天甚至幾周,特別是當(dāng)涉及到硬件時(shí)。這種緩慢的速度令人感到煎熬,會(huì)間接導(dǎo)致更高的成本,修復(fù)兩周前出現(xiàn)的問題比修復(fù)一小時(shí)前出現(xiàn)的問題更難,因?yàn)樵谶@期間發(fā)生了更多的變化。
具有諷刺意味的是,成本如此之高的測(cè)試套件通常在發(fā)現(xiàn) Bug 方面表現(xiàn)不佳。一方面,被測(cè)試的軟件經(jīng)常會(huì)有些部分不會(huì)被測(cè)試到。另一方面,它們通常包含了大量的冗余,測(cè)試到的部分會(huì)被重復(fù)測(cè)試。這些部分的 Bug 會(huì)導(dǎo)致成百上千的測(cè)試失敗。因此,這些測(cè)試套件既不有效(因?yàn)樗鼈儨y(cè)試不到某些部分)也不高效(因?yàn)樗鼈儼哂鄿y(cè)試)。
當(dāng)然,這并不是一個(gè)新發(fā)現(xiàn)。大多數(shù)團(tuán)隊(duì)早就放棄了針對(duì)每一個(gè)變更執(zhí)行整個(gè)測(cè)試套件,甚至也不針對(duì)每一個(gè)新發(fā)布版本執(zhí)行整個(gè)測(cè)試套件。相反,他們要么每隔幾周執(zhí)行一次(這會(huì)延遲發(fā)現(xiàn) Bug,導(dǎo)致修復(fù)它們的成本變得更高),要么只執(zhí)行測(cè)試套件的子集(這會(huì)遺漏許多本可以被發(fā)現(xiàn)的 Bug)。
本文提供了更好的解決方案,使用來(lái)自被測(cè)系統(tǒng)和測(cè)試本身的數(shù)據(jù)來(lái)優(yōu)化測(cè)試工作。團(tuán)隊(duì)可以在更短的時(shí)間內(nèi)(通過(guò)減少執(zhí)行不太可能檢測(cè)到 Bug 的測(cè)試)發(fā)現(xiàn)更多的 Bug(確保測(cè)試了 Bug 密集區(qū)域)。
通過(guò)分析開發(fā)過(guò)程數(shù)據(jù)來(lái)優(yōu)化測(cè)試
如果一個(gè)測(cè)試套件效率低下,那么其后果對(duì)開發(fā)和測(cè)試團(tuán)隊(duì)來(lái)說(shuō)是顯而易見的:測(cè)試工作量很大,但仍然會(huì)有很多 Bug 不會(huì)被發(fā)現(xiàn),并進(jìn)入生產(chǎn)環(huán)境。
然而,由于在大型組織中,沒有人擁有完整的信息,對(duì)于如何解決這個(gè)問題(或者是誰(shuí)的錯(cuò)),通常會(huì)有不同的——經(jīng)常是沖突的——意見?;诓糠中畔⑻岢龅挠^點(diǎn)很難被證實(shí)或反駁,如果人們把注意力放在與他們的觀點(diǎn)相一致的東西上,而不是大局,那么團(tuán)隊(duì)(或團(tuán)隊(duì)的團(tuán)隊(duì))在很長(zhǎng)一段時(shí)間內(nèi)都不會(huì)有改進(jìn)。
例如,有時(shí)候,經(jīng)驗(yàn)豐富的測(cè)試人員會(huì)責(zé)怪開發(fā)人員在實(shí)現(xiàn)新特性時(shí)破壞了太多現(xiàn)有功能。作為回應(yīng),測(cè)試人員將更多的精力放在回歸測(cè)試上。與此同時(shí),開發(fā)人員指責(zé)測(cè)試人員測(cè)試新功能 Bug 的速度太慢。隨著測(cè)試人員把更多的精力放在回歸測(cè)試上,新特性中的 Bug 會(huì)在更晚的時(shí)候才有可能被發(fā)現(xiàn)。由于開發(fā)人員很晚才發(fā)現(xiàn)新特性中的 Bug,所以修復(fù)通常是在回歸測(cè)試完成之后。如果這樣的修復(fù)在不同的位置導(dǎo)致 Bug 出現(xiàn),測(cè)試人員就沒有機(jī)會(huì)通過(guò)回歸測(cè)試捕獲它們。
具有諷刺意味的是,這種局面為兩個(gè)團(tuán)隊(duì)的觀點(diǎn)提供了支撐,讓兩個(gè)團(tuán)隊(duì)更加認(rèn)為他們的觀點(diǎn)是正確的,但這也讓問題變得更糟。
這些團(tuán)隊(duì)必須停止?fàn)幷撘话阈缘膯栴},比如原則上需要多少回歸測(cè)試,他們要做的是看一下數(shù)據(jù),看看現(xiàn)在一個(gè)特定的變更需要哪些測(cè)試。軟件存儲(chǔ)庫(kù),如版本控制系統(tǒng)、問題跟蹤器或持續(xù)集成系統(tǒng),包含了大量與軟件相關(guān)的數(shù)據(jù),這些數(shù)據(jù)可以幫助我們基于數(shù)據(jù)而不是意見對(duì)測(cè)試做出優(yōu)化。
為此,我們基本上可以對(duì)在軟件開發(fā)期間收集了大量數(shù)據(jù)的存儲(chǔ)庫(kù)進(jìn)行分析,這樣就可以得出一些與測(cè)試過(guò)程有關(guān)的問題的答案。

過(guò)去哪里出現(xiàn)的 Bug 最多?我們可以從中了解到什么?
版本歷史記錄和問題跟蹤器包含了過(guò)去在哪里修復(fù)過(guò) Bug 的信息。這些信息可以被提取出來(lái),并用于計(jì)算不同組件的 Bug 密度。

這顯示了在一個(gè)系統(tǒng)中,一個(gè)組件的每行代碼修復(fù)密度比系統(tǒng)平均修復(fù)密度高了一個(gè)數(shù)量級(jí),如上面的藍(lán)色矩形樹圖所示。每個(gè)矩形代表一個(gè)文件,其面積與文件的代碼行數(shù)相對(duì)應(yīng)。藍(lán)色陰影越深,表示這個(gè)文件越是經(jīng)常進(jìn)行 Bug 修復(fù)。
在矩形樹圖的中心位置有一簇文件,藍(lán)色比其他部分更深。
下面的矩形樹圖顯示了自動(dòng)化測(cè)試的覆蓋范圍。白色表示未覆蓋,綠色表示測(cè)試覆蓋的增加(綠色越深表示覆蓋越多)。值得注意的是,中心的組件包含了大量的歷史 Bug,但幾乎沒有被自動(dòng)化測(cè)試覆蓋。
經(jīng)過(guò)與團(tuán)隊(duì)的討論,發(fā)現(xiàn)了這個(gè)組件測(cè)試過(guò)程的一個(gè)系統(tǒng)性問題:開發(fā)人員已經(jīng)為其他所有組件編寫了單元測(cè)試,但缺少為這個(gè)組件編寫單元測(cè)試的測(cè)試框架。開發(fā)人員已經(jīng)提了一個(gè)改進(jìn)測(cè)試框架的 Ticket。在實(shí)現(xiàn)所需的測(cè)試框架之前,他們系統(tǒng)性地跳過(guò)了為這個(gè)組件編寫的單元測(cè)試。由于團(tuán)隊(duì)不知道這些 Bug 的影響,導(dǎo)致 Ticket 一直呆在 backlog 沒有被解決。
在上述的分析揭示了 Bug 的影響之后,Ticket 很快就得到了解決,并編寫了缺失的單元測(cè)試。在此之后,這個(gè)組件中的新 Bug 數(shù)量不再高于其他組件。
未測(cè)試的變更(測(cè)試間隙)在哪里
測(cè)試間隙(Gap)是指未被測(cè)試到的新代碼或修改過(guò)的代碼的區(qū)域。團(tuán)隊(duì)通常會(huì)特別仔細(xì)地嘗試測(cè)試新代碼和修改過(guò)的代碼,因?yàn)閺闹庇X(和經(jīng)驗(yàn)研究)上看,它們比沒有被修改的代碼區(qū)域包含更多的 Bug。
測(cè)試間隙分析通過(guò)結(jié)合兩個(gè)數(shù)據(jù)源來(lái)揭示測(cè)試間隙:版本控制系統(tǒng)和代碼覆蓋信息。
首先,我們從版本控制系統(tǒng)中計(jì)算兩個(gè)軟件版本(例如上一個(gè)版本和要發(fā)布的下一個(gè)版本)之間的所有變更,因?yàn)楦鶕?jù)我們的直覺(和經(jīng)驗(yàn)研究)認(rèn)為這些區(qū)域是最容易出錯(cuò)的。

這個(gè)矩形樹圖描繪的是一個(gè)包含大約 150 萬(wàn)行代碼的業(yè)務(wù)信息系統(tǒng)。30 名開發(fā)人員花了 6 個(gè)月的時(shí)間來(lái)準(zhǔn)備下一個(gè)版本。每個(gè)白色矩形表示一個(gè)組件,每個(gè)黑色矩形表示一個(gè)代碼函數(shù)。組件和函數(shù)的面積與代碼行數(shù)相對(duì)應(yīng)?;疑匦沃械拇a自上次發(fā)布以來(lái)沒有發(fā)生變化。紅色矩形是新代碼,橙色矩形是修改過(guò)的代碼。樹圖顯示了哪些區(qū)域變化相對(duì)較?。ɡ缱蟀氩糠郑?,哪些區(qū)域變化很大(例如右側(cè)的組件)。
其次,我們收集了所有測(cè)試覆蓋率數(shù)據(jù)。收集過(guò)程是完全自動(dòng)化的,不管是對(duì)于自動(dòng)測(cè)試還是手動(dòng)測(cè)試來(lái)說(shuō)。更具體地說(shuō),我們使用代碼覆蓋概要來(lái)捕獲發(fā)生的所有測(cè)試活動(dòng)的測(cè)試覆蓋信息。不同的編程語(yǔ)言甚至是不同的編譯器可能需要不同的分析器,但它們通常適用于所有較為流行的編程語(yǔ)言。

這個(gè)樹圖顯示了上述系統(tǒng)的測(cè)試覆蓋率。它結(jié)合了自動(dòng)化測(cè)試(在本例中是單元測(cè)試和集成測(cè)試)和手動(dòng)測(cè)試(5 名測(cè)試人員花了一個(gè)月執(zhí)行手動(dòng)系統(tǒng)級(jí)回歸測(cè)試)的覆蓋范圍?;疑匦伪硎緶y(cè)試期間未被執(zhí)行的函數(shù),綠色矩形表示已被執(zhí)行的函數(shù)。
最后,我們將這些信息結(jié)合起來(lái),找到那些沒有被測(cè)試到的變更,從而找出所謂的測(cè)試間隙。

在這個(gè)樹圖中,我們并不關(guān)心沒有發(fā)生變更的代碼。因此,它們使用灰色表示(與測(cè)試期間是否被執(zhí)行無(wú)關(guān))。新代碼和修改過(guò)的代碼用不同的顏色表示:如果是在測(cè)試期間執(zhí)行的,則用綠色表示。如果不是,則新代碼用紅色表示,修改的代碼用橙色表示。
在這個(gè)例子中(在計(jì)劃發(fā)布日期的前一天),我們看到幾個(gè)組件(包含數(shù)萬(wàn)行代碼)在測(cè)試期間根本沒有被執(zhí)行。
團(tuán)隊(duì)可以基于測(cè)試間隙分析慎重決定是否要將這些測(cè)試間隙(即未測(cè)試的新代碼或修改過(guò)的代碼)交付到生產(chǎn)環(huán)境中。在某些情況下,這不是問題(例如,如果未測(cè)試的功能還沒有被使用),但通常情況下最好要對(duì)關(guān)鍵功能進(jìn)行額外的測(cè)試。
在上面的例子中,團(tuán)隊(duì)決定不發(fā)布,因?yàn)槲幢粶y(cè)試的功能非常重要。發(fā)布被推遲了三周,大多數(shù)測(cè)試間隙被數(shù)以千計(jì)額外的(手動(dòng)和自動(dòng))測(cè)試用例覆蓋到了,他們因此可以捕捉(并修復(fù))關(guān)鍵的 Bug。
現(xiàn)在哪些測(cè)試最有價(jià)值
如果我們對(duì)代碼變更和測(cè)試覆蓋率進(jìn)行持續(xù)的分析,就可以自動(dòng)計(jì)算出自上次測(cè)試套件執(zhí)行以來(lái)哪些代碼發(fā)生了變更。我們可以明確地選擇那些執(zhí)行了這些代碼區(qū)域的測(cè)試用例。相比重新運(yùn)行所有測(cè)試,運(yùn)行這些受影響的測(cè)試用例能夠更快地發(fā)現(xiàn)新 Bug(因?yàn)椴簧婕白兏臏y(cè)試無(wú)法發(fā)現(xiàn)由這些變更引入的新 Bug)。
這種測(cè)試影響分析加速了開發(fā)人員的反饋。根據(jù)我們的經(jīng)驗(yàn),我們發(fā)現(xiàn)我們可以在 1% 的時(shí)間(運(yùn)行整個(gè)測(cè)試套件所花費(fèi)的時(shí)間)內(nèi)發(fā)現(xiàn) 80% 的 Bug(運(yùn)行整個(gè)測(cè)試套件所能發(fā)現(xiàn)的 Bug),或者在 2% 的時(shí)間內(nèi)發(fā)現(xiàn) 90% 的 Bug(關(guān)于變更驅(qū)動(dòng)測(cè)試的更多細(xì)節(jié)可以參看這里)。

一般來(lái)說(shuō),哪些測(cè)試最有價(jià)值
一些測(cè)試本身就需要消耗昂貴的資源。例如,我們的一些客戶在昂貴的硬件上執(zhí)行測(cè)試套件。每個(gè)測(cè)試運(yùn)行包括數(shù)萬(wàn)個(gè)單獨(dú)的測(cè)試,需要數(shù)周的時(shí)間來(lái)執(zhí)行,并集成來(lái)自不同團(tuán)隊(duì)的組件。而這些測(cè)試又很重要,因?yàn)闆]有這些測(cè)試軟件就不能發(fā)布。
對(duì)于這種大型、昂貴的測(cè)試套件來(lái)說(shuō),一個(gè)真正的問題是“海量 Bug”:一個(gè)位于中心位置的 Bug 會(huì)導(dǎo)致數(shù)百甚至數(shù)千個(gè)測(cè)試用例失敗。如果被測(cè)試的系統(tǒng)版本包含大量 Bug,那么整個(gè)測(cè)試就會(huì)被破壞,因?yàn)樵跀?shù)以千計(jì)失敗的測(cè)試中很難再進(jìn)一步找到其他 Bug。因此,測(cè)試團(tuán)隊(duì)在開始執(zhí)行大型、昂貴的測(cè)試套件之前,需要確保所測(cè)試的系統(tǒng)不包含質(zhì)量缺陷。
為了防止出現(xiàn)海量 Bug 現(xiàn)象,團(tuán)隊(duì)使用了驗(yàn)收測(cè)試套件(也叫作冒煙測(cè)試套件),在執(zhí)行大型、昂貴的測(cè)試之前必須先通過(guò)驗(yàn)收測(cè)試套件。驗(yàn)收測(cè)試套件將執(zhí)行所有測(cè)試中的一小部分,這些測(cè)試極有可能會(huì)發(fā)現(xiàn)會(huì)導(dǎo)致眾多測(cè)試失敗的 Bug。
我們可以根據(jù)特定的代碼覆蓋率信息從現(xiàn)有的測(cè)試集中選擇一個(gè)最佳的驗(yàn)收測(cè)試套件(在最短的時(shí)間內(nèi)覆蓋最多的代碼)。我們發(fā)現(xiàn)所謂的“貪婪”優(yōu)化算法比較有效:它們從一個(gè)空集開始,逐步添加每秒覆蓋最多代碼行的測(cè)試。然后,它們繼續(xù)添加那些覆蓋了還沒有被之前的測(cè)試覆蓋到的代碼行的用例。重復(fù)這個(gè)選擇過(guò)程,直到驗(yàn)收測(cè)試套件的時(shí)間預(yù)算用完為止。在我們的研究中,我們發(fā)現(xiàn)我們用這種方法選擇的驗(yàn)收測(cè)試套件可以在 6% 的時(shí)間內(nèi)(執(zhí)行整個(gè)測(cè)試套件所需的時(shí)間)找到 80% 的 Bug(整個(gè)測(cè)試套件可以檢測(cè)到的 Bug)。

我們?cè)谝粋€(gè)項(xiàng)目中將這種選擇驗(yàn)收測(cè)試套件的方法與測(cè)試專家手動(dòng)選擇的驗(yàn)收測(cè)試套件進(jìn)行了比較。對(duì)于前兩年的歷史測(cè)試執(zhí)行數(shù)據(jù),優(yōu)化選擇的驗(yàn)收測(cè)試套件發(fā)現(xiàn)的 Bug 比專家手動(dòng)選擇的套件多兩倍。
這種方法不如測(cè)試影響分析(只需要 1% 的時(shí)間就能找到 80% 的 Bug),但是當(dāng)可用的信息較少時(shí)(我們不需要知道自最后一次執(zhí)行測(cè)試以來(lái)的所有代碼變更),可以使用這種方法。
如何在項(xiàng)目中開始使用測(cè)試智能分析
測(cè)試智能分析可以為各種問題提供基于數(shù)據(jù)驅(qū)動(dòng)的答案。因此,你很有可能會(huì)嘗試使用它們,看看它們能揭示出有關(guān)系統(tǒng)的哪些信息。
如果從正在被測(cè)試的系統(tǒng)的特定問題開始會(huì)更加有效,變更管理會(huì)因此更有可能取得成功,因?yàn)檎f(shuō)服同事和經(jīng)理解決問題比嘗試新工具來(lái)得更容易。
根據(jù)我們的經(jīng)驗(yàn),在使用測(cè)試智能分析之前,可以先考慮以下這些問題:
1.是否有太多的 Bug 從測(cè)試環(huán)境進(jìn)入到生產(chǎn)環(huán)境?通常,造成 Bug 的根本原因就是測(cè)試間隙(即尚未被測(cè)試的新代碼或修改過(guò)的代碼區(qū)域)。測(cè)試間隙分析有助于在發(fā)布之前發(fā)現(xiàn)并解決它們。
2.整個(gè)測(cè)試套件的執(zhí)行時(shí)間是否太長(zhǎng)?測(cè)試影響分析可以識(shí)別出 1% 發(fā)現(xiàn) 80% 新 Bug 的測(cè)試用例,這大大縮短了反饋周期。
一旦采用了測(cè)試智能分析,就可以很容易地用它們來(lái)回答其他問題。因此,團(tuán)隊(duì)很少只使用一種分析手段。但要解決實(shí)質(zhì)性的問題,總是要賣出第一步。
作者簡(jiǎn)介
Elmar Jürgens 寫了一篇關(guān)于靜態(tài)分析的博士論文,現(xiàn)在仍然活躍在軟件質(zhì)量分析領(lǐng)域。2009 年,他聯(lián)合創(chuàng)立了 CQSE GmbH,幫助開發(fā)團(tuán)隊(duì)更有效地使用分析工具。Jürgens 經(jīng)常在研究會(huì)議(如 ICSE、ICPC)和行業(yè)活動(dòng)(如 GTD、OOP、JAX)上發(fā)言。Jürgens 在 2015 年被評(píng)為 ACM 德國(guó)分會(huì)的 Junior Fellow。
查看英文原文:
https://www.infoq.com/articles/process-data-find-more-bugs/