軟件測試修煉之道之——重現問題
運用實證(實證依賴的是觀察和經驗,而不是理論和純邏輯推理)方法進行調試可以充分利用軟件的獨特能力來告訴你軟件運行的狀態,而發揮這種能力的關鍵是找到能夠重現問題的方法。
為什么重現問題如此重要?
不能重現問題,就幾乎不可能取得進展,因為實證過程依賴于我們觀察存在缺陷的軟件執行的能力。
如何重現?
要做的第一件事很簡單,就是按照缺陷報告描述(或提示)的步驟做,要做好問題重現就要抓好控制,而需要控制的因素如下:
1、軟件本身:如果缺陷存在于最近修改的地方,那么你應該首先保證你調試運行的軟件和缺陷被提交時使用的軟件是同一個版本
2、軟件的運行環境:如果要與外部系統進行交互,那么可能需要確保你使用的是相同的外部系統
3、你提供的輸入:如果一段代碼的運行情況受軟件的配置影響很大,而缺陷又與這段代碼有關,那么應該使用用戶的配置來進行調試
控制軟件本身:最好的辦法就是創建一個自動化的構建過程
控制運行環境:要知道缺陷出現時軟件所使用的環境,記住:軟件環境包括了可能影響軟件運行的所有因素。
控制輸入:要找出輸入數據以便準確重現問題。如果無法獲得需要的所有信息,有兩種選擇:一是推測一下可能的輸入是什么,二是把它們記錄下來。
推測可能輸入:出發點是假設問題確實存在,然后運用逆向工程的方法推測出能夠導致問題的必要條件。
回溯:通常我們知道發生了什么事,但不清楚為什么會發生,此時可以使用回溯法,如果運氣好,這個邏輯可以直接重現問題,即使不能完全重現,也可以提供有用的線索,再加上其他異常的現象,就可以排除某些可能性。
探測可能的輸入值:黑盒技術中邊界值分析可以在這里使用,因為輸入值范圍的邊界是最有可能出現錯誤的地方,而相當于白盒測試中的邊界值分析的分支覆蓋,也可以在這里派上用場,可以嘗試創建一些輸入,使用這些輸入可以覆蓋同一代碼塊中的不同分支。
有效地識別能重現問題的輸入順序需要你轉變思路——你不必證明系統工作正常,而要證明它不正常。
利用錯誤條件:當試圖重現一個問題的時候,考慮一下是否有一些錯誤條件出現在運行的過程中,并去解釋為什么問題會發生,然后,想想如何使錯誤條件表現出來或模擬出來,并且是否可以使你重現問題。
引入隨機性:選取一系列不同輸入值的一種方法就是引入一些隨機輸入值。
在推測重現問題時需要使用的數據過程中,記住:你需要驗證與缺陷報告不符的結論。你找到了一種使軟件出現缺陷的方法,并不意味著你找出了缺陷報告中的缺陷(盡管你已經清楚地發現了一個需要修正的缺陷)。
記錄輸入值:使用日志記錄輸入值。獲取日志最簡單的方法就是在整個代碼中正確地放置了對System.out.printfln()或類似方法的調用,當然,也可以使用日志框架來完成更為復雜的功能(此處略去該方法)。
是否應該把日志留在代碼中?
答案仁者見仁智者見智,如果在代碼中嵌入日志,無疑,當問題再次發生時可以快速尋找到它,但是,它卻容易導致代碼變得難以理解,同時,它將類似注釋內容而停滯,一成不變。因此,最佳的選擇是注重實效,一旦將它嵌入代碼,確保你的日志隨時更新,與代碼保持一致,而非為了日志而做日志。
負載和壓力:關于負載測試工具的問題,通常是找到一個辦法讓它們重現真實的負載,但創建大量簡單的交互行為可能并不會產生足夠的負載來重現需要調試的問題。解決方法之一是使用日志記錄真實的負載量,然后使用負載測試工具去重現它。
壓力測試工具也是類似的,只是它不直接產生負載。
問題重現曾經是一個重要的障礙,下面我們將聊聊如何改進問題重現。不管是什么樣的重現問題的方法,只要有,就比沒有強。但是如何讓重現問題既可靠又方便呢?
最小化反饋周期:和軟件開發的其他眾多領域一樣,問題重現也是要使反饋周期最小,所經過的周期越短,反饋就越及時,其相關性就越高。
因此,最先要關注的就是找出問題重現中哪些方面是不需要的,將它們剔除掉,稱為將問題重現最小化。那么,哪些元素可以被剔除呢?往往這要靠直覺。你了解軟件,并且知道哪些模塊可能被一些特定輸入所影響,如果直覺不對,那么一些非直接的方法可能會幫助到你。
改進問題重現不是一蹴而就的事,而是在整個診斷過程中要牢記在心的東西。
將不確定的缺陷變為確定的:要做到這一點,需要明白不確定性從何來?
1、開始于不可預知的初始狀態
當你的軟件從未經初始化的內存讀取數據時,通常會出問題。如果你有理由相信,是這個原因導致了不確定性問題,那么你最好的選擇可能是使用調試內存分配器,來強制內存被初始化為一個眾所周知的值,或用內存完整性檢驗軟件來檢測是否引用了未初始化的內存。
2、與外部系統進行交互
由此引起的不確定性問題往往不是因為二者表現不一,而是因為時間上微妙的不同。解決的策略是能夠精確控制從外部系統接收了什么,以及何時接收的。最好的選擇可能不是試圖直接控制外部系統,而是用你能控制的東西替換它,比如調試子系統或代替測試。
3、故意地使用隨機性
由此導致的不確定性聽起來還是很正常的,幸運的是,大部分所謂使用隨機數的軟件都是通過確定性算法產生的偽隨機數,因此這個是完全可以預測的行為。
4、多線程
由此引起的不確定性最難處理。在多核系統盛行的今天,往往我們處理的并不是真正的并發。而在缺乏并發控制的結構化方法下,我們不得不依靠一些特殊的方法。因此,在處理方法中最有效的工具之一就是不起眼的sleep()方法,它允許你強制一個線程長時間等待而出現競爭狀態。
例如,假設你正在工作的軟件中多個乖哦工作線程并行處理工作項目,工作線程使用下面的java代碼來獲得工作項目:
if (item=workQueue.lockWorkItem()) { item.process(); workQueue.writeResultAndUnlock(item); } |
你試圖跟蹤一個間歇出現的缺陷,有時同一個工作項目會同時分配給兩個工作線程。遺憾的事,這種情況極少出現,那么,你可以將代碼更改為如下形式來增加重現該問題的幾率:
if (item=workQueue.lockWorkItem()) { Thread.sleep(1000); item.process(); workQueue.writeResultAndUnlock(item); } |
注意:盡管sleep()方法在重現問題和診斷階段很有用,但在修復缺陷階段它不是一個適合的方法。
自動化:一個自定義測試不僅能夠方便的運行,而且當診斷結束開始修復的時候,對于即將編寫完成的測試來說,它是一個很好的起點。如果確定缺陷重現需要通過日志,那么可以選擇重放日志文件。
迭代:在診斷的過程中,你構建了足夠多關于如何以及為何軟件如此運行的信息,可以用此不斷改進重現,如下圖:
通過如下步驟反復改進:
1、你確定一個特定的模塊已包含在內,其中有導致缺陷的元素,這樣可以創建一個更小的文件。
2、進一步診斷,發現能通過用樁模塊替代與第三方服務器交互的子系統,讓問題每次都出現,樁模塊可以很容易返回已經確定的響應。
3、最后,把跟蹤范圍縮小到一個特定函數,通過設置一組具體的參數調用該函數來創建單元測試,以便重現問題。
如果真的不能重現問題該怎么辦?
首先,不要輕易給出“缺陷不存在”的結論,除非盡力獲取了更多額外信息,用盡了所有可用的辦法依然不能重現問題。其次,在相同區域解決不同的問題,盡管沒有你目前跟蹤的缺陷那么嚴重和緊急,也許它們蒙蔽了真正缺陷所在,也許會讓你找到重現問題的關鍵因素,當然,也許沒有任何幫助。第三,試著讓其他人參與其中,這樣可以獲取其他人的不同角度看待問題,尤其是反饋錯誤的人。第四,充分利用用戶群體,有些時候,缺陷出現在外部系統而非開發系統中,但這需要用戶為你收集所需要的信息,從某種角度來說,并不理想。最后,可以使用推測法,你所需要做的是把自己融入到軟件中,在想象中執行軟件,執行每一步,考慮有哪些出現錯誤的可能性,嘗試解釋你跟蹤的缺陷。
正常來說,我們有能力重現問題,而在下一章中,我們將會看到如何用重現來診斷問題。
posted on 2013-03-27 10:33 順其自然EVO 閱讀(383) 評論(0) 編輯 收藏 所屬分類: 測試學習專欄