生活中有這么一種現象:如果你關注某些東西,它就會經常出現在你眼前,例如一個不出名的歌手的名字,一種動物的卡通形象,某個非常專業的術語,等等等等。這種現象也叫做“孕婦效應”。還有類似的一種效應叫做“視網膜效應”,它講的是:你有什么東西或者特質你就特別容易在別處發現你有的這類東西和特質。干了多年測試的我就會經常發現日常使用的系統中有很多的bug,而我老婆就發現不了。今天要說的事兒是“重現難以重現的bug”,這件事兒在本周共遇見了4次:第一次是微博上有一篇《程序員,你調試過的最難的 Bug 是?》(后面會附上);第二次是一個同事跟我抱怨,好幾個bug難以重現特心煩,並問我怎么處理比較好;第三次是本周上線的產品出現了一個當時難以重現的bug,我們對它做了初步的分析;第四次是翻看史亮寫的書《軟件測試實戰》,偶爾翻了翻,竟然看到一小節在寫“處理難以處理的缺陷”。這時候,腦子里很多東西匯集到了一起,我想還是記錄一下吧。下面是正文:
也許測試人員(尤其是對新手來說)在工作過程中最不願遇到的一件事情就是:在測試過程中發現了一個問題,覺得是bug,再試的時候又正常了。碰到這樣的事情,職業素養和測試人員長期養成的死磕的習性會讓她們覺得不能放過這個bug,但是重現這樣的bug有時候需要花費大量的時間,有的時候還有一些盲目性(因為黑盒測試的緣故,很多內部狀態是不可見的,因此無法獲取有效的信息來做跟蹤),效率較為低下。在實際工作中,時間和進度擺在那里,在經歷了多次痛苦的失敗嘗試之后,測試人員的處理方法一般會有如下幾種:1.向開發人員尋求幫助來重現bug;2.當做一個issue報給開發人員。可是這樣的做法存在如下問題:
1.開發人員責任心不夠強,不願意花太多精力去求證這件事情,常見的回復就是:在我這兒沒事兒啊,我也重現不了,bug關了吧。結果隨后在生產系統上,bug又開始sui隨機出現了。
2.就跟測試人員不擅長編碼和調試一樣,開發人員並不擅長找出bug。經過一番嘗試以后,他們也找不出什么問題來,常見的回復同第一條是一樣的。bug上線后又出現的宿命也是一樣的。
這時候,真正的問題來了:如何捕捉難以重現的bug?這件事兒對於測試人員來說就這么難么?
答案並不那么樂觀,重現“難以”重現的bug本來就是一件“難以”完成的事情。但“難以”並不是不可能,通過一系列的測試、分析方法,我們是能夠抽絲剝繭把絕大部分隱藏的很深的bug揪出來的,當然有的時候你要考慮投入產出比,但投入產出比不是本篇要考慮的,本篇只講一些我積累的經驗。
為什么不能重現bug?
最大的原因就是:測試人員對被測物的了解還不夠深入。
我曾經做過一段很長時間的收集和統計,那些被稱作過“難以重現”的bug最后都可以分為如下幾類:
1.環境的變更造成了bug難以重現,這里的環境包括了:基礎軟硬件環境(操作系統、網絡、存儲、中間件、容器等等),被測物自身發生了某些變更。環境的變更一般是由於多人共用環境造成的,也有少量情況下是系統內部或者時間觸發的變更(這類bug非常難重現)。
2.沒有找到真正引發bug的操作。這些操作往往是一些不怎么顯而易見的操作,測試人員在不經意間完成,而又忽略了這一操作,以致難於重現bug。
3.沒有找到真正會引發bug的操作序列。很多bug的重現需要滿足多個條件。在滿足多個條件的狀態下,你做了對應的操作,bug才會被觸發。
4.bug必須使用特殊的數據才會出現,測試人員沒有意識到她使用的數據的特殊性。一種比較難搞的情況是:同一組輸入,在不同情況下(不是不同的業務情況)輸入會被轉化成不同的數據。我曾經見到過這么個例子,程序員用系統當前時間作為隨機數的種子來生成id,但是id設置的比較短,一個存儲的操作使用這個id,在一些情況下,發生了沖突,當時做功能測試這種小概率事件耗費了測試人員大量時間也沒有穩定重現,還是在性能測試的階段測試了出來。
5.測試人員由於錯誤操作,出現了誤報(這很常見)。比如,記得自己執行了step3,其實沒有,或者沒有正確執行step卻覺得正確執行了。
怎樣對付這樣的bug呢?
我喜歡James Bach 說的那句話:測試就像CSI。CSI是Criminal Scene Investigation 的縮寫,也是我非常喜歡的美國系列劇。
從我來看CSI的精髓在於:仔細觀察,詳細記錄,科學分析,嚴密推理,有序求證,大膽假設,持續不懈,團隊協作和一點兒運氣。找bug其實和CSI探員做犯罪現場調查沒什么太大區別。得知道,你工作的重要性一點兒不亞於CSI探員。
仔細觀察:第一件事情就是要觀察,觀察你所做的一切操作和一切相關的系統反饋。在一開始,觀察的重要性要遠遠大於思考,通過觀察你獲得蛛絲馬跡,這些蛛絲馬跡是你進行思考和假設的關鍵輸入。例如,我在一次測試的過程中,發現做某種操作的時候會相當慢,極少數情況下還報錯過一兩次,當詢問了開發人員后得知這個操作的后台實現步驟是:先查看數據是否在緩存中,如果不在,則從遠端服務器請求數據。我抓住少數情況下會報錯的這一現象,仔細觀察它的出錯信息后猜測報錯並不是因為網絡連接不穩定引起的,而是由於遠端服務接口實現有問題引起的,后來重新設計了測試用例,果然找到了問題所在。如果不仔細觀察出錯信息,就會聽信開發人員認為這是網絡不穩定引發的正常issue而錯過這個bug。
詳細記錄:詳細記錄你的操作步驟及返回結果非常有助於回朔問題,也有助於后續分析。准備一個word文檔,和截圖工具有時候非常必要。另外,在觀察的時候,你不僅要注意被測物的最終返回,還需要觀察過程中的一些中間狀態,往往這些中間狀態提供的信息才是解開問題的關鍵。這些中間狀態一般會被記錄在log文件中,因此知道你的被測物是如何記log的,log被記錄在哪里非常重要。log給了你另外一個看系統的角度。log是有級別的,如果級別可以動態調整,在找比較難找的bug時,可以將log記錄的級別調至最低(DEBUG級)讓它們記錄更多內容。利用系統的錯誤轉儲文件(比如linux的core dump文件,windows下也有相應的記錄轉儲文件的方式)分析也是個不錯的辦法(需要較高技術能力),但一般建議測試人員把這些轉儲文件交給更專業的開發人員來分析。在我短暫的C++開發歲月中,有使用過GDB閱讀轉儲文件的經歷,那絕對不是愉快的回憶。你瞧,測試人員的主要工作是找出可重現的bug,並不是定位它們,不是么?
除了log,如果能有監控信息,也要查看他們。比如系統提供的監控平台,監控日志;應用自己的監控平台、監控日志(如果有的話);采用一些外部技術手段截取一些中間狀態信息,如使用sniffer抓取通訊包,使用Fiddler截獲HTTP報文內容;給運行程序插樁來查看內存,堆棧,線程,函數被調用情況等情況,如Jprofile,gpertool等等。
科學分析:對於黑盒測試人員來說,科學分析意味着你需要有一定的分析策略。我們需要采取一些形式化的方法來完成我們的分析。基於我的統計,缺陷難以重現有很大一部分原因是因為“沒有找到真正引發bug的操作序列“。測試人員不可能像開發人員那樣去跟入到代碼內部,設置斷點調試程序,他們主要的測試方式是直接來操縱被測物,並從外部觀察被測物狀態的改變。顯而易見,“狀態轉換圖分析法”是測試人員對付“難以重現bug”的最強有力武器之一。狀態轉化圖能夠幫助測試人員很好的選擇操作路徑,並且知道這么做有什么意義。“狀態圖轉化法”絕對是測試人員值得花時間學習和研究的一種方法,你可以走的很深,也可以研究得很遠(可以從MBT的方向進行拓展),限於篇幅,這里就不展開了。在這里推薦《探索吧!深入理解探索式軟件測試》這本書,它的第八章對“狀態轉換”做了非常實用的描述。
上文分析的讓bug難於重現的另一種原因是沒有找到“真正引發bug的特殊數據”。我的常用做法是這樣的:1.畫出系統交互圖(要真正弄清系統的邊界,這很重要),並識別出每種交互會有什么樣的輸入、輸出數據和中間數據,識別出這些數據的規約和格式,這樣你就不會對數據有遺漏。2.考慮數據的等價類、邊界值,對這些輸入進行組合,分析數據之間是否有耦合關系,如果有耦合關系,弄明白關系是什么,設計違背這些關系的用例,最后采用組合測試工具初步生成測試集,再人工選取最可能出問題的數據集(直覺有時候非常管用)。
嚴密推理:天馬行空對測試人員很重要,但是當你試圖重現一個bug的時候,這並不是一個非常好的方法。抓住了蛛絲馬跡,你就要推理是為什么產生了這種蛛絲馬跡。限於工作性質,測試人員更多的會從:業務完整性、數據完整性、業務正確性、數據正確性等方面考慮問題。但是,如果測試人員對被測物的IT架構有比較深入了解的話,推理的范圍會擴大到技術實現層面,如:多線程可能引發的問題,網絡引發的問題,excepiton處理不當引發的問題,全局事務設計不當引發的問題,內存泄漏引發的問題,數據庫表設計不合規引發的問題等等等等,這些會讓你的分析推理能力如虎添翼。當然,如果限於條件,測試人員不具備這類能力,則應該在適當的時候請求開發人員協助。
有序求證:這里只有一點需要注意。那就是,在求證的過程中不要打散彈槍,按照你的推理一步一步的來,一個個推理的來驗證,一次只引入一處修改。這樣才能讓你的捕蟲網編制的足夠細密。
大膽假設:有的時候,看似八竿子打不着的東西竟然存在着千絲萬縷的聯系,而你獲取信息的過程總是一個由少及多的過程,一開始這些聯系是無法被識別出來的。通過推理,逐步驗證,你慢慢的識別出了大部分內在聯系。但有時候這種逐步推進的工作也會有局限性,工作如果出現了瓶頸(你試遍了你所有的假設,都沒有重現bug),這時候就需要發揮一點兒想象力了,天馬行空這時候從一定程度上又變得有用起來,當然天馬行空也不是無厘頭,還得靠我們所謂的“靈光一閃”,這號稱是潛意識在幫助你。CSI的劇情中不也總是出現這種柳暗花明的橋段么?
堅持不懈:話不多說,有的時候你差的就是那么一點兒點兒耐心。
團隊協作:很多情況下,重現bug不是一個人能搞定的。我們需要測試環境ready,測試數據ready,各種監控、分析工具ready,各種不同的視角開拓思路、加深對被測試物的認識。這是team work!!!獨行俠有時候很管用,但是終究有極限。這就是為什么CSI是一票人在做而不是一兩個人在做。
一點兒運氣:說實在的,有的時候重現bug就是靠運氣,你不得不承認這一點。事實上很多美好的事情發生都得依靠運氣,比如中彩票。要記住的一點是,運氣是建立在你不懈努力的基礎上的。如果你一張彩票不買,你肯定什么也中不了。但如果你堅持買上幾年,中個五塊十塊甚至二百也不是夢。
Let it go:全試過了,連運氣都沒有。你只能放手,回到最上面我說的那兩條了:找開發來幫忙,或者給開發報issue。btw,即使不能重現bug,也應該給開發人員提供更多信息:如log、dump文件、監控記錄、屏幕截圖等。做一個負責人的測試人員,把煩惱真實的留給下家,這很重要:)
最后給出一個軟件調試大牛 David.A.Agans對於軟件調試的九條建議,非常值得一讀:
http://sydney.edu.au/engineering/aeromech/MTRX2700/Course%20Material/lectures/PDF/week04/Debugging.pdf
9月25日:今天學到了一個詞:Heisenbug ,這詞的中文意思可以被翻譯為“神出鬼沒的bug”。。。這個單詞和量子力學元勛海森堡的名字差了一個字(Heisenburg) 量子力學的一個經典定理就是"測不准原理"。 大家的吐槽能力真強。 想了解細節請看下面的這篇文章:
http://blog.csdn.net/kjfcpua/article/details/8125306
9月26日:覺得有一些實例會更容易理解,我會盡量收集一些例子放到這個帖子里:
記一次w3wp占用CPU過高的解決過程(Dictionary和線程安全)
12月3日更新: 一個Ajax同步異步引發的血案 一個hotfix的debug過程:)
12月3日:其實上面的大段論述是站在測試人員角度上來看的。我也寫很多代碼,作為一個開發人員(哈哈)我查錯的方式一般是:
1.先能穩定的復現錯誤。(這一步最難)
2.設樁,打印出一些中間狀態來分析,看到那一塊兒錯了。
3.摘除不相干的代碼,慢慢迭代,定位錯誤。
4.如果發現調用第三方依賴跟你想的不一樣,先讀文檔,然后再測試第三方依賴。有問題再想辦法。
5.良好的程序設計和單元測試覆蓋度才是不二法門,會讓你的調試變得簡化的太多。。。用過都說好:)
6.測試工作的確增強了我排錯的能力。coding的同學都嘗試做幾個月測試吧。會有相當大的好處。
14年12月15日:早上的一個同事提出了一個小技巧,覺得很不錯,記錄一下:如果你沒有辦法穩定的復現bug,可以通過概率統計的方式將問題反饋。如果10次里出現一次問題,不足以打動開發人員重視問題,可以通過自動化測試的方式提高執行次數的數量級,如果一千次出現50次~100次。這個問題就足夠引起重視了。在negotiate的時候,這是一種好思路。
----------分割線,下面是轉載的文章--------------------------------