單元測(cè)試常見(jiàn)問(wèn)題及測(cè)試方法

? 黑盒測(cè)試由于看不到代碼內(nèi)部的實(shí)現(xiàn)邏輯,經(jīng)常出現(xiàn)漏測(cè)或測(cè)試不到位的情況,因?yàn)橛行撛诘膯?wèn)題通過(guò)黑盒測(cè)試不好復(fù)現(xiàn),但code review就很容易發(fā)現(xiàn)問(wèn)題所在,那我們?cè)赾ode review時(shí)應(yīng)該注意哪些點(diǎn)呢?

首先了解單元測(cè)試中驅(qū)動(dòng)代碼,樁代碼和Mock代碼三者的邏輯關(guān)系

單元測(cè)試常用方法.png

驅(qū)動(dòng)代碼(Driver)指調(diào)用被測(cè)函數(shù)的代碼:?jiǎn)卧獪y(cè)試中,驅(qū)動(dòng)模塊通常包括調(diào)用被測(cè)函數(shù)前的數(shù)據(jù)準(zhǔn)備、調(diào)用被測(cè)函數(shù)及驗(yàn)證相關(guān)結(jié)果三個(gè)步驟。
樁代碼(Stub)是用來(lái)代替真實(shí)代碼的臨時(shí)代碼。比如,某個(gè)函數(shù)A的內(nèi)部實(shí)現(xiàn)中 調(diào)用了一個(gè)尚未實(shí)現(xiàn)的函數(shù)B,為了對(duì)函數(shù)A的邏輯進(jìn)行測(cè)試,那么就需要模擬一個(gè)函數(shù)B,這個(gè)模擬的函數(shù)B的實(shí)現(xiàn)就是所謂的樁代碼。
ex:假定函數(shù)A是被測(cè)函數(shù),其內(nèi)部調(diào)用了函數(shù)B(偽代碼如下):
A函數(shù)調(diào)用B函數(shù).png

被測(cè)函數(shù)A內(nèi)部調(diào)用了函數(shù)B
(在單元測(cè)試階段,由于函數(shù)B尚未實(shí)現(xiàn),但為了不影響對(duì)函數(shù)A自身邏輯的測(cè)試,我們可以用一個(gè)假函數(shù)B來(lái)代替真實(shí)函數(shù)B,那么假函數(shù)B就是樁函數(shù))

? 為了實(shí)現(xiàn)函數(shù)A的全路徑覆蓋,我們需要控制不同的測(cè)試用例中函數(shù)B的返回值,那么樁函數(shù)B的偽代碼就應(yīng)該是這個(gè)樣子:


裝代碼B函數(shù).jpg

這樣就覆蓋了被測(cè)函數(shù)A的if-else的兩個(gè)分支

(當(dāng)執(zhí)行第一個(gè)測(cè)試用例的時(shí)候,樁函數(shù)B應(yīng)該返回true,而當(dāng)執(zhí)行第二個(gè)測(cè)試用例的時(shí)候,樁函數(shù)B應(yīng)該返回false)

樁代碼的作用:起到隔離和補(bǔ)齊的作用,使被測(cè)代碼能夠獨(dú)立編譯、鏈接,并獨(dú)立運(yùn)行。同時(shí),樁代碼還具有控制被測(cè)函數(shù)執(zhí)行路徑作用

Mock代碼和樁代碼非常相似,都是用來(lái)代替真實(shí)代碼的臨時(shí)代碼,起到隔離和補(bǔ)齊的作用

Mock代碼和樁代碼本質(zhì)區(qū)別:測(cè)試期待結(jié)果的驗(yàn)證(Assert and Expectiation)

  • 對(duì)于Mock代碼來(lái)說(shuō),我們關(guān)注點(diǎn)是Mock方法有沒(méi)有被調(diào)用,以什么樣的參數(shù)被調(diào)用,被調(diào)用的次數(shù),以及多個(gè)Mock函數(shù)的先后調(diào)用順序,所以,在使用Mock代碼的測(cè)試中,對(duì)于結(jié)果的驗(yàn)證(也就是assert),通常出現(xiàn)在Mock函數(shù)中
  • 對(duì)于樁代碼來(lái)說(shuō),我們關(guān)注點(diǎn)是利用Stub來(lái)控制被測(cè)函數(shù)的執(zhí)行路徑,不會(huì)去關(guān)注Stub是否被調(diào)用以及怎樣被調(diào)用。所以,在使用Stub的測(cè)試中,對(duì)于結(jié)果驗(yàn)證(也就是assert),通常出現(xiàn)在驅(qū)動(dòng)代碼中

就算不能分清Mock代碼和樁代碼,也不影響單元測(cè)試。深入比較,參考 馬丁.福勒(Martin Fowler)的著名文章《Mock代碼不是樁代碼》

實(shí)際項(xiàng)目中如何開(kāi)展單元測(cè)試?
? 1、并不是所有代碼都要進(jìn)行單元測(cè)試,通常只有底層模塊或則核心模塊的測(cè)試才會(huì)采用單元測(cè)試
? 2、確定單元測(cè)試框架選型,這和開(kāi)發(fā)語(yǔ)言有關(guān)。比如Java最長(zhǎng)用的單元測(cè)試框架是Junit 和TestNG
? 3、為了能夠衡量單元測(cè)試的代碼覆蓋率,需要引入代碼覆蓋率工具。不同的語(yǔ)言會(huì)有不同的代碼覆蓋率工具。比如Java的JaCoCo,JavasScipt的Istanbul

代碼覆蓋率工具的實(shí)現(xiàn)原理
? 實(shí)現(xiàn)代碼覆蓋率的統(tǒng)計(jì),最基本的方法就是注入,在被測(cè)代碼中自動(dòng)插入用戶覆蓋率統(tǒng)計(jì)的探針(Proble)代碼,并保證插入的探針代碼不會(huì)給源代碼帶來(lái)任何影響

常見(jiàn)的代碼級(jí)錯(cuò)誤
? 1、語(yǔ)法特征錯(cuò)誤:從編程語(yǔ)法上就能發(fā)現(xiàn)的錯(cuò)誤,如:不符合編程語(yǔ)言語(yǔ)法的語(yǔ)句

數(shù)組越界.png

? 數(shù)組越界,訪問(wèn)了未被初始化的內(nèi)存空間,代碼運(yùn)行時(shí)就會(huì)造成意想不到的結(jié)果。黑盒測(cè)試還不一定測(cè)得到,比如我們測(cè)試有遇到的數(shù)據(jù)越界問(wèn)題:刪除購(gòu)物車商品偶爾會(huì)造成程序閃退,但從黑盒測(cè)試上是不能必現(xiàn)的,只能從代碼層面去找問(wèn)題

? 2. 邊界值行為特征錯(cuò)誤:代碼在執(zhí)行過(guò)程中發(fā)生異常,崩潰或者超時(shí),此類錯(cuò)誤通常發(fā)生在一些邊界條件上


邊界值特征錯(cuò)誤.png

? 以上代碼就存在具有邊界行為特征錯(cuò)誤。當(dāng)b取值為0時(shí),Division函數(shù)就會(huì)拋出運(yùn)行時(shí)異常

? 3.經(jīng)驗(yàn)特征錯(cuò)誤:根據(jù)過(guò)往經(jīng)驗(yàn)發(fā)現(xiàn)代碼錯(cuò)誤


經(jīng)驗(yàn)特征錯(cuò)誤.png

? 代碼想要表達(dá)的意思:如果變量i的值等于2,就調(diào)用函數(shù)operationA,否則調(diào)用函數(shù)operationB。

? 但是,代碼中將“if(i==2)" 錯(cuò)誤地寫成了" if(i=2)",就會(huì)使原本的邏輯判斷操作變成賦值操作,而且這個(gè)賦值操作的返回結(jié)果永遠(yuǎn)是true,即這端代碼永遠(yuǎn)只會(huì)調(diào)用operationA的分支。

? 顯然,“if(i=2)”在語(yǔ)法上沒(méi)有錯(cuò)誤,但是從過(guò)往經(jīng)驗(yàn)來(lái)看,這就很可能是個(gè)錯(cuò)誤了

? 4、算法錯(cuò)誤:代碼完成的計(jì)算(或則功能)和之前預(yù)先設(shè)計(jì)的計(jì)算結(jié)果(或則功能)不一致。
這類錯(cuò)誤直接關(guān)系到代碼需要實(shí)現(xiàn)的業(yè)務(wù)邏輯,在整個(gè)代碼級(jí)測(cè)試中所占比重最大,也是最重要的。但是,完全的算法錯(cuò)誤不常見(jiàn),因?yàn)椴荒軠?zhǔn)備完成基本功能需求的代碼,是一定不會(huì)被提交的。所以,項(xiàng)目中最常見(jiàn)的是部分算法錯(cuò)誤。

? 5、部分算法錯(cuò)誤:在一些特定條件或則輸入情況下,算法不能準(zhǔn)確完成業(yè)務(wù)要求實(shí)現(xiàn)的功能。


部分代碼錯(cuò)誤.png

? 這段代碼,完成了兩個(gè)int類型整數(shù)的加法運(yùn)算。在大多數(shù)情況下,這段代碼的功能邏輯都是正確的,能夠準(zhǔn)確地返回兩個(gè)整數(shù)的加法之和,但是,在某些情況下,可能存在兩個(gè)很大的整數(shù)相加后“和"越界的情況,也就是說(shuō)兩個(gè)很大的int數(shù)相加的結(jié)果超過(guò)了int的范圍。這是典型的部分算法錯(cuò)誤。

代碼級(jí)測(cè)試常用方法

代碼級(jí)測(cè)試常用方法.jpg

自動(dòng)靜態(tài)方法能夠以極低的成本發(fā)現(xiàn)以下問(wèn)題:

  • 使用未初始化的變量;
  • 變量在使用前未定義;
  • 變量聲明了但未使用;
  • 變量類型不匹配;
  • 部分內(nèi)存泄漏的問(wèn)題;
  • 空指針引用;
  • 緩沖區(qū)溢出;
  • 數(shù)組越界;
  • 不可達(dá)的僵尸代碼;
  • 過(guò)高的代碼復(fù)雜度;
  • 死循環(huán);
  • 大量的重復(fù)代碼塊;
    ....
    實(shí)現(xiàn)方式:企業(yè)結(jié)合自己的編碼規(guī)范定制度規(guī)程庫(kù),并于本地的IDE開(kāi)發(fā)環(huán)境和持續(xù)集成流水線整合

代碼本地開(kāi)發(fā)階段,IDE環(huán)境就可以自動(dòng)對(duì)代碼實(shí)現(xiàn)自動(dòng)靜態(tài)檢查;當(dāng)代碼提交到倉(cāng)庫(kù)后,CI/CD流水線自動(dòng)觸發(fā)代碼靜態(tài)檢查,如果檢查到潛在錯(cuò)誤,就會(huì)自動(dòng)發(fā)郵件通知代碼遞交者。

? 如圖:C語(yǔ)言代碼存在數(shù)組越界的問(wèn)題,通過(guò)C語(yǔ)言的自動(dòng)靜態(tài)掃描工具splint發(fā)現(xiàn)這個(gè)問(wèn)題,并給出分析結(jié)果。

靜態(tài)掃描結(jié)果.png

動(dòng)態(tài)測(cè)試方法也就是單元測(cè)試方法,看似簡(jiǎn)單,但在實(shí)際工程中會(huì)遇到很多困難:
? 1、單元測(cè)試用例”輸入?yún)?shù)“的復(fù)雜性,表現(xiàn)在”輸入?yún)?shù)“不是簡(jiǎn)單的函數(shù)輸入?yún)?shù)。本質(zhì)上,任何能夠影響代碼執(zhí)行路徑的參數(shù),都是被測(cè)函數(shù)的輸入?yún)?shù)。
? 2、單元測(cè)試用例”預(yù)期輸出“的復(fù)雜性,主要表現(xiàn)在”預(yù)期輸出“應(yīng)該包括被測(cè)函數(shù)執(zhí)行完成后所改寫的所有數(shù)據(jù)
? 3、關(guān)聯(lián)代碼不可用,需要采用樁代碼模擬不可用代碼,并通過(guò)打樁補(bǔ)齊未定義部分

單元測(cè)試用例 ”輸入?yún)?shù)“的復(fù)雜性
函數(shù)內(nèi)部調(diào)用子函數(shù)獲得數(shù)據(jù)

函數(shù)內(nèi)部調(diào)用子函數(shù).png

? 函數(shù) Func_SUT 是被測(cè)函數(shù),它的內(nèi)部調(diào)用了函數(shù) FuncX,函數(shù) FuncX 的返回值是 bool 類型,并賦值給內(nèi)部變量toggle,之后代碼會(huì)根據(jù)變量toggle的取值來(lái)決定執(zhí)行哪個(gè)代碼分支。那么,被測(cè)函數(shù)內(nèi)部調(diào)用子函數(shù)獲得的數(shù)據(jù)也是單元測(cè)試的輸入?yún)?shù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容