【NCTS峰會回顧】餓了么邱化峰:人工智能在Bug定位中的應(yīng)用
2019年10月26日,由Testin主辦的第二屆NCTS中國云測試行業(yè)峰會在京召開,此次峰會以“AI+未來”為主題,匯聚來自國內(nèi)外測試領(lǐng)域的知名專家學(xué)者、領(lǐng)先企業(yè)決策者、高層技術(shù)管理者、媒體從業(yè)者等,共同探討高端云測試技術(shù),幫助測試從業(yè)者了解最前沿行業(yè)趨勢,及最新的行業(yè)實踐。
會上,餓了么測試開發(fā)專家邱化峰做《人工智能在Bug定位中的應(yīng)用》主題演講。邱化峰指出,“企業(yè)想獲得人工智能帶來的便利,首先要制訂相對應(yīng)的標(biāo)準(zhǔn),如果沒有這個標(biāo)準(zhǔn),按照固定的算法,則很難實現(xiàn)。”
以下為邱化峰演講實錄:
今天我講的是人工智能在Bug應(yīng)用中的定位,講這個話題之前,我想先做一下統(tǒng)計,大家用java管理Bug的人員有多少?在修復(fù)Bug之后,在代碼里面,想關(guān)聯(lián)Bug是什么樣的同學(xué)請舉手。人工智能定位首先需要有一個數(shù)據(jù)的基礎(chǔ),這個數(shù)據(jù)的來源是什么樣的?測試通過java提交Bug,我們有Bug的描述,Bug的相關(guān)信息,以及Bug的重現(xiàn)步驟,會直接提給開發(fā),開發(fā)根據(jù)這些步驟去執(zhí)行,看Bug是不是可重現(xiàn)的。如果是可重現(xiàn)的,會要求開發(fā)把相關(guān)的問題進(jìn)行關(guān)聯(lián),關(guān)聯(lián)之后作為Bug分析的一個前提,不需要在人工去標(biāo)注什么樣的樣本,因為把Bug描述的相關(guān)信息放上之后,我們就可以通過自然語言或者是人工的處理方式,提取含有Bug相關(guān)的字段,比如出現(xiàn)的異常是什么,錯誤是什么,問題是什么樣的。有了這個基礎(chǔ)之后,不需要人去做標(biāo)注,就可以拿到一些樣本。還有一個是學(xué)術(shù)界要做的,首先,有些人專門維護(hù)了所有關(guān)于java的Bug,這些Bug在修復(fù)里時會生成一些數(shù)據(jù),告訴你哪個方法在第幾層,哪個方法哪一行出現(xiàn)了什么錯誤。如果想使用人工智能帶來的便利,首先要制訂一些相對應(yīng)的標(biāo)準(zhǔn),如果沒有這個標(biāo)準(zhǔn),很難按照固定的算法來得到想要得到的東西。講完了這些前提,我主要講三部分,第一是工具介紹,第二是Bug定位的原理和方法,第三,使用這些工具之后應(yīng)用從哪些方面做具體的落地。
工具分了八種,這個PPT是之前做的,最近一個月又出來一個新的Bug定位的技術(shù),就是基于行為的Bug定位技術(shù)。前面這八種定位技術(shù)都是以能拿到相關(guān)的樣本來做對應(yīng)的處理,而且這些東西是通過提取測試用例,編譯測試。這大部分是基于單元測試做的,但是基于行為的已經(jīng)突破了在單元測試的層面去獲取Bug的樣本,通過基于鏈路去分析是什么樣的情況。第一個是程序頻譜,就是我跑多少測試用例,成功的測試用例覆蓋了哪些行,失敗的覆蓋哪些行。我們計算失敗的測試用例占的行,我們做一個排名,就可以拿到這一行代碼出現(xiàn)BUG的概率是多少。然后是基于變體,程序頻譜的基礎(chǔ)上做了一個改良,變體現(xiàn)在應(yīng)用的,最實用的一個是衡量測試用例的有效性很重要的一個手段,我寫了那么多測試用例,這些測試用例是不是真的有效,我可以去變體來做衡量。基于變體會生成兩個,有一個變異分?jǐn)?shù),分?jǐn)?shù)越高的時候,單元的測試用例的質(zhì)量就比較高。程序切片就是通過程序切片或者場景,生成程序用例的方式,程序切片也可以用于Bug定位,然后是堆棧跟蹤,APP端有用監(jiān)控的,類似于接了監(jiān)控系統(tǒng)的同學(xué)舉一下手。安卓系統(tǒng)底下的應(yīng)用,比較多的只發(fā)安卓的,不發(fā)iOS的同學(xué)有多少?大部分都是既發(fā)安卓又發(fā)iOS?;诙褩W粉櫍褩T掝}要講到好多,比如日志,log,線上的這些東西怎么去拿。堆棧追蹤,不僅僅應(yīng)用于服務(wù)端的Bug的定位,也可以應(yīng)用于C端的Bug定位,比如界面突然卡掉了,或者突然卡死,我拿到這些對應(yīng)的堆棧信息,也可以直接來進(jìn)行Bug的定位,以及做Bug的預(yù)測。第五是上下文的切換,這個就不講了。第六是信息檢索,通過一些詞頻,或者去算出現(xiàn)概率比較多的,大家知道熱點覆蓋概念的同學(xué)請舉一下手,你能說一下嗎?
聽眾:有個熱點圖,看哪邊用戶點擊更多,哪邊點擊更少。
邱化峰:我們是變向的熱點覆蓋的方法,映射到修改的方法是最多的。最后是機(jī)器學(xué)習(xí)。如果我們需要知道怎么去定位Bug,通過人工智能的方式,首先要定義各種指標(biāo),這些指標(biāo)就決定了在定位Bug中的準(zhǔn)確率有多高?;趈ava這里列了7個,首先看一下,代碼的有效行數(shù),圈復(fù)雜度,包括是否符合javadoc的規(guī)范,為什么要把這個作為參考指標(biāo)?阿里巴巴推出了java編寫規(guī)范,不是說憑空無中生有的,是基于掃描了阿里所有的代碼倉庫包括歷史記錄總結(jié)出來的,比如空格應(yīng)該空幾行。我們有人工智能定位的工具,通過統(tǒng)計含有Bug的類型做了一個統(tǒng)計,Bug產(chǎn)生因素最多的就是隨意的命名變量名,這是作為了定位Bug中得出來的最主要的參考指標(biāo)。Bug的產(chǎn)生大部分是由人的理解或者認(rèn)知在一定范圍之內(nèi)造成錯誤的使用的某個變量名,包括函數(shù)。代碼編寫規(guī)范,第一代是函數(shù)式的,第二代是面向?qū)ο蟮?,面向?qū)ο蟮氖遣皇侨f能的,我們面向?qū)ο笕懙臅r候,是不是易于維護(hù)的和易于理解的。最主要的有一個是扇入和👕扇出,我去做Bug定位的時候,如果這個方法被多次引用,也會增加Bug出現(xiàn)的概率。然后是包的設(shè)計質(zhì)量,之前是單體的應(yīng)用,現(xiàn)在講微服務(wù),又在講中臺,這些是相當(dāng)于在包的基礎(chǔ)上做了一些變革,之前我們是單體的,把它拆成微服務(wù)之后應(yīng)該怎么做,當(dāng)把微服務(wù)單獨抽出來做中臺,最主要的改變就是Jar包的結(jié)構(gòu)和這些東西也在隨之不斷的發(fā)生變化。第四個,用來分析Jar包的升級質(zhì)量,變量的個數(shù)和參數(shù)的個數(shù),這也是在Bug中影響B(tài)ug定位的主要指標(biāo),大家見過的,能知道測試過程中發(fā)現(xiàn)變量數(shù)最多的同學(xué),有沒有說發(fā)現(xiàn)使用了多少參數(shù),或者一個函數(shù)能支持多少個函數(shù),有知道的同學(xué)嗎?參數(shù)的個數(shù),語言多多少少有差異,我在函數(shù)傳參的時候,函數(shù)的參數(shù)名和變量最多只允許有256個,超過256個,程序就跑不起來了。變量的個數(shù)和參數(shù)也是作為Bug定位的指標(biāo)。再看最后一個,運(yùn)算符的個數(shù),大家也知道功能測試和做中間件測試的,基本上是不一樣的,不一樣在哪兒?基于業(yè)務(wù)的可能是業(yè)務(wù)場景的,邏輯,或者是業(yè)務(wù)場景相對來說比較固定,邏輯的單一化比較好一點,但是作為一個中間件,會考慮很多的不確定因素。當(dāng)我們?nèi)タ紤]這些不決定因素的時候,其實是變向增加了運(yùn)算符的操作。
第二部分,原理和方法。我們剛才也給大家普及了基礎(chǔ)的東西,這部分的training set,都是來自于實際中編寫的代碼或者是樣例,是作為Bug預(yù)防的第一代原形,相對來說比較簡單,只用了一個圈復(fù)雜度,知道圈復(fù)雜度的同學(xué)舉一下手,為什么可以作為預(yù)測Bug出現(xiàn)概率的重要指標(biāo)?就是圈復(fù)雜度會根據(jù)你寫的代碼,里面有多少分支,走多少流程,跟圈復(fù)雜度是對照的。一個函數(shù)里面,如果圈復(fù)雜度,工業(yè)級別上的,好比軍工的軟件,一般檢測出來不超過7,或者更嚴(yán)格的,代碼復(fù)雜度不能超過5,當(dāng)圈復(fù)雜度越高的時候,就證明寫的函數(shù)體越來越長,但是如果在5和7以內(nèi),大概40行左右,超過7以上,到20了,代碼行數(shù)就有可能變成幾千行了,幾千行的函數(shù),無論是讓原來的開發(fā),還是接手的新開發(fā)來去做這個事情,都變得很難維護(hù)。既然是我們能知道圈復(fù)雜度有這樣的功能,我們可以用圈復(fù)雜度來預(yù)測提測代碼之后,去掃一下圈復(fù)雜度,看是不是在合理的范圍內(nèi)之內(nèi),如果不在合理的范圍之內(nèi),可能會有一定的概率出現(xiàn)Bug。
這個圖里面介紹了學(xué)術(shù)界應(yīng)用,把加入的代碼出現(xiàn)的Bug解決掉,做了一個樣本,沒有修Bug之前代碼是怎么寫的,修完Bug之后代碼又是如何寫的。
這是基于Bug定位出現(xiàn)的第二個原形,用來預(yù)測Bug缺陷。從第一個原形到第二個原形,中間已經(jīng)多了很多步驟,首先需要有一個dataset。第二是前面說的學(xué)術(shù)界的獲取dataset的方法。第三是用人工自然語言的去處理,不用java管理的,或者不跟javaBug關(guān)聯(lián)的,但是有很多開發(fā)去直接寫,用英文也好或者用中文注釋也好,會說某某個問題,這是一般開發(fā)都會做的。第三個是獲取樣本的方法。將來可能出現(xiàn)Bug的因素有哪些,這是這些因素做了排序和處理,做Bug定位或者預(yù)測的時候,首先得知道哪個指標(biāo)應(yīng)該占什么樣的比例,用剛才的方法,就去不斷調(diào)整各種指標(biāo)占的比例,然后根據(jù)所占的這些比例多少,判斷實際得到的和最終現(xiàn)實發(fā)生的真實的Bug匹配度,在人工智能中有一個專業(yè)名詞是召回率,來看最終的結(jié)果,這是屬于第二代的原形。在這個原形里面,有一個test的樣本,大部分是使用變異測試來做的。
這是基于java的,參考各種因素和指標(biāo),能得到一些樣例結(jié)果的展示。每種定位技術(shù),得到的能定位的結(jié)果差異也是有很多的,如果大家想去看具體的實現(xiàn),在這個底下有一個連接,把種種技術(shù),包括方法,源碼,結(jié)果,都生成一個統(tǒng)計結(jié)果,可以到里面看具體的執(zhí)行情況是什么樣的。
這是堆棧,現(xiàn)在的堆棧拋出,一般情況下,無論是前端也好,還是后端也好,都要求封裝。Exception,堆棧有多個方法,異常的會提示說屬于什么樣的異常,既有基于系統(tǒng)級別的,也有中間件定義的,也有用戶行為定義出來的,這個時候我們不能單純的說用它做缺陷預(yù)防,我們首先要把三個分層,不能直接簡單拿過來用,基于堆棧去預(yù)測這種情況,這樣就會更加準(zhǔn)確。這個里面會告訴你某一個類,某一個方法,底下具體是哪一行到哪一行,我們基于熱點覆蓋的時候經(jīng)常會提一個概念,拿到修改的行,通過修改的行去匹配,這個行的代碼就會明確的告訴你我在這行拋了,開發(fā)去解決問題的時候,就有經(jīng)驗的開發(fā),會做一件事情,永遠(yuǎn)去看0或者1的,不去看后面的,后面再拋出來的跟現(xiàn)在是完全不一樣的。既然開發(fā)都能夠通過這個來做,我們可以轉(zhuǎn)化為一個缺陷預(yù)防,或者是判斷的技術(shù)手段,這個時候我們就可以去針對這些拋出來的Exception,再做一個排名,需要收集線上所有的日志里面,把所有日志里面無論這類也好,還是這個方法也好,只要在里面出現(xiàn)一次,我將排名就加“1”,會首先看全局的哪類哪個方法,即使行數(shù)不一樣,我要統(tǒng)計到這個方法底下服務(wù)端的排名情況。
我剛才所說的都是為如何建立我的缺陷預(yù)防的指標(biāo)的參考依據(jù),大家可以看到這些指標(biāo),比如有多少個鏈路的調(diào)用,總共有多少函數(shù)處在這個破拋出來的這些堆棧的異常相關(guān)信息,十前面的ST作為基礎(chǔ)數(shù)據(jù),我們發(fā)現(xiàn)只是用基礎(chǔ)數(shù)據(jù)去預(yù)測Bug出現(xiàn)Stack Trace概率并不是很準(zhǔn)確,到底出現(xiàn)Bug的概率是什么樣的,我們相當(dāng)于做了一個類似于加權(quán)平均,或者統(tǒng)計學(xué)里面應(yīng)該叫取一個均值,把參數(shù)做一個綜合,舉個簡單的例子,如果這個項目中只有兩個文件,如果拋出來一個堆棧異常,兩個類里面有多少個,和我有10個文件,但是每個類里面的代碼行數(shù)對應(yīng)的行數(shù)不同,如果想全面的了解到Bug出現(xiàn)的概率,就不能簡單的以其中一個指標(biāo)作為參考。做了一個組合之后,就相當(dāng)于把出現(xiàn)Bug的概率和情況做了一個更加詳細(xì)的指標(biāo)定義。我們有了這個指標(biāo)的定義,就更容易預(yù)測Bug出現(xiàn)的概率。
這是堆棧信息人工智能預(yù)測的模型,把Stack Trace和錯誤的代碼進(jìn)行了關(guān)聯(lián),做了一個提取和模型的建立,并且自動的把它打標(biāo),第二個階段,我已經(jīng)有了線上這么多出現(xiàn)的異常,我們新來一個,能不能直接定位到某一類某一行的方法上。為什么要做這個事情?現(xiàn)在測試不是左移就是右移,對于我們來說,跟開發(fā)合作的時候,他認(rèn)為比較理想的測試是說不用給那么多步驟,不用那么多方法,能不能寫一個單元測試,能不能直接告訴我問題出在哪兒。有很多的方法是希望測試做到這樣的,并不是說測試做不到,測試也是可以做到的,但是需要很多的工具,包括歷史數(shù)據(jù)的積累才能做到。而且有很多的情況,測試對于場景,包括業(yè)務(wù)了解得更多,這里只寫了模塊的一小部分,如果把測試的這些反饋到開發(fā)身上,就可以做到這一點。
這是第三代的技術(shù),會把靜態(tài)分析和動態(tài)分析做整合,把代碼的覆蓋率,方法的覆蓋率,包括分支的覆蓋率,統(tǒng)一放到矩陣?yán)锩?,在這個矩陣相對應(yīng)的,可以認(rèn)為原生的第一代是一維的,只有一個東西幫我們做判定,這個是一個二維的,到了第三代其實是做了更多維度的,但是這個維度里面既包含了靜態(tài),又把動態(tài)的也放進(jìn)來了,同時也把覆蓋的行這些東西也都做到統(tǒng)一的PageRank Matrix里,從多個角度去看能不能預(yù)測這個Bug出現(xiàn)的概率。
最終,我們會得到一個Rank List,我們統(tǒng)一做各種排序,再去看預(yù)測Bug出現(xiàn)的概率就會比之前更加準(zhǔn)確。
還有一個是基于行為的,也是最近剛剛出現(xiàn)的用來定位Bug的,就不展開描述了。
然后我們說一下怎么應(yīng)用,程序頻譜是一種有效的Bug定位的方法,作為卡殼的時候,首先不能單純的說以指標(biāo)來衡量,需要把它作為綜合的指標(biāo)去看整體的提測質(zhì)量之后,就會發(fā)現(xiàn)之間是有正相關(guān)的,或者呈線性的數(shù)據(jù)的指標(biāo),代碼寫得越爛,出現(xiàn)的Bug概率就會越高,這是毋庸置疑的。第二,Stack Trace對于Crash的定位是很有效的,現(xiàn)在手機(jī)經(jīng)常運(yùn)行著的時候就不動的,或者響應(yīng)比較慢,我們?nèi)ツ玫蕉褩5腻e誤異常,上報上來,這個時候?qū)τ谌ザㄎ贿@些異常是很有效的,用它來提高安卓端的響應(yīng),包括出現(xiàn)什么樣的錯誤,這是十分有效的手段。第三,錯誤的樣本集目前仍然不夠大,剛才說的三種方式,還漏掉很多沒有按照規(guī)范和固定操作制度,中間有很多開發(fā)沒有按照這個去做,有一個叫全樣本,這也是息息相關(guān)的因素。還有一個,算法的多樣性,我們用靜態(tài)和動態(tài)的調(diào)用,里面的算法也是多樣性的,目前有20多個算法,算法也決定了預(yù)測Bug出現(xiàn)概率的重要參考因素。很顯然,第一代的不如第二代的,第二代的不如第三代的,但是這些迭代的版本、方法多了之后,原來簡單的算法也不適應(yīng)了,每增加一個因素,或者每增加一個參考的因子,模型也要做相對應(yīng)的處理,這樣才能預(yù)測Bug出現(xiàn)的情況。
現(xiàn)場視頻(下)
通過這些Bug定位的技術(shù)和方法,我們拿到最直接、最有效的方式。把它作為實地落地的情況,通過這些能夠?qū)ξ业钠髽I(yè)產(chǎn)生直接收益,一般情況下,前面分析了那么多,但是這些東西都不能落地,或者都不能提高協(xié)同的效率,推廣和應(yīng)用是很困難的,我們通過它可以做到的有這四個場景,能夠提高大家的效率,輔助開發(fā)來定位Bug。阿里內(nèi)部有一個可視化的質(zhì)量分,分了四個維度,第一是圈復(fù)雜度,第二是面向?qū)ο蟮闹笜?biāo),第三個我忘記了,每個指標(biāo)里面都有因子,每個方面給一個權(quán)重,提測完代碼之后進(jìn)行開發(fā),跟代碼的質(zhì)量分是不是有直接關(guān)聯(lián)。 指標(biāo)定義好,就可以發(fā)現(xiàn)問題,指標(biāo)定義不好就會出笑話。
Bug的重復(fù)檢測,如果我們做交叉,或者是比較大的,比如我們使用外包,免費(fèi)的從網(wǎng)上公開共測的東西,他們提過的Bug之后,我們也可以去做去重,來決定將來判定一個重要的指標(biāo)。Bug的自動assign,每個開發(fā)提交的,根據(jù)這個場景涉及到的case,或者提交分支版本,可以將Bug和這個產(chǎn)生者直接做一個關(guān)聯(lián),出現(xiàn)了Bug就可以不需要判定是哪個開發(fā)要做的事情,可以告訴是這個東西出了問題。然后是case的等級分類,相信大家在做第四個的時候,都應(yīng)該會做,如果不做case的等級分類,就是測試連入門都沒有做到。為什么要把它提出來?因為我們發(fā)現(xiàn)現(xiàn)在有很多的工具也可以幫我們生成單元測試的代碼,但是這些單元測試的代碼和這個之間并沒有作為強(qiáng)關(guān)聯(lián)。大家都知道“8020”法則,80%的Bug來自于20%的代碼,我們所有case也不應(yīng)該在同一個級別,可以針對這個理論,對這些20%的代碼做case等級的分類。迭代的版本越多,開發(fā)和測試不斷在維護(hù)自己自動化的腳本,包括單元測試的腳本,這些東西會慢慢的越積越多,這個時候之前第一個版本跑了10分鐘就能跑完,但是運(yùn)行了半年,或者是一年以上,這個項目沒有死,你會發(fā)現(xiàn)測試用例一直往上堆,之前跑10分鐘,半個小時就能跑完,現(xiàn)在是用空間換時間看case執(zhí)行的情況。我們除了用空間換時間之外,還可以用等級分類的方法把這些case標(biāo)志出來,可以用少一些的空間,或者是少一些的時間。
這是四篇論文,同學(xué)可以自己去網(wǎng)上找,研究一下哪些對你們來說是可以直接落地的。
今天的分享就到這兒。