單元測試常見問題及測試方法

? 黑盒測試由于看不到代碼內部的實現邏輯,經常出現漏測或測試不到位的情況,因為有些潛在的問題通過黑盒測試不好復現,但code review就很容易發現問題所在,那我們在code review時應該注意哪些點呢?

首先了解單元測試中驅動代碼,樁代碼和Mock代碼三者的邏輯關系

單元測試常用方法.png

驅動代碼(Driver)指調用被測函數的代碼:單元測試中,驅動模塊通常包括調用被測函數前的數據準備、調用被測函數及驗證相關結果三個步驟。
樁代碼(Stub)是用來代替真實代碼的臨時代碼。比如,某個函數A的內部實現中 調用了一個尚未實現的函數B,為了對函數A的邏輯進行測試,那么就需要模擬一個函數B,這個模擬的函數B的實現就是所謂的樁代碼。
ex:假定函數A是被測函數,其內部調用了函數B(偽代碼如下):
A函數調用B函數.png

被測函數A內部調用了函數B
(在單元測試階段,由于函數B尚未實現,但為了不影響對函數A自身邏輯的測試,我們可以用一個假函數B來代替真實函數B,那么假函數B就是樁函數)

? 為了實現函數A的全路徑覆蓋,我們需要控制不同的測試用例中函數B的返回值,那么樁函數B的偽代碼就應該是這個樣子:


裝代碼B函數.jpg

這樣就覆蓋了被測函數A的if-else的兩個分支

(當執行第一個測試用例的時候,樁函數B應該返回true,而當執行第二個測試用例的時候,樁函數B應該返回false)

樁代碼的作用:起到隔離和補齊的作用,使被測代碼能夠獨立編譯、鏈接,并獨立運行。同時,樁代碼還具有控制被測函數執行路徑作用

Mock代碼和樁代碼非常相似,都是用來代替真實代碼的臨時代碼,起到隔離和補齊的作用

Mock代碼和樁代碼本質區別:測試期待結果的驗證(Assert and Expectiation)

  • 對于Mock代碼來說,我們關注點是Mock方法有沒有被調用,以什么樣的參數被調用,被調用的次數,以及多個Mock函數的先后調用順序,所以,在使用Mock代碼的測試中,對于結果的驗證(也就是assert),通常出現在Mock函數中
  • 對于樁代碼來說,我們關注點是利用Stub來控制被測函數的執行路徑,不會去關注Stub是否被調用以及怎樣被調用。所以,在使用Stub的測試中,對于結果驗證(也就是assert),通常出現在驅動代碼中

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

實際項目中如何開展單元測試?
? 1、并不是所有代碼都要進行單元測試,通常只有底層模塊或則核心模塊的測試才會采用單元測試
? 2、確定單元測試框架選型,這和開發語言有關。比如Java最長用的單元測試框架是Junit 和TestNG
? 3、為了能夠衡量單元測試的代碼覆蓋率,需要引入代碼覆蓋率工具。不同的語言會有不同的代碼覆蓋率工具。比如Java的JaCoCo,JavasScipt的Istanbul

代碼覆蓋率工具的實現原理
? 實現代碼覆蓋率的統計,最基本的方法就是注入,在被測代碼中自動插入用戶覆蓋率統計的探針(Proble)代碼,并保證插入的探針代碼不會給源代碼帶來任何影響

常見的代碼級錯誤
? 1、語法特征錯誤:從編程語法上就能發現的錯誤,如:不符合編程語言語法的語句

數組越界.png

? 數組越界,訪問了未被初始化的內存空間,代碼運行時就會造成意想不到的結果。黑盒測試還不一定測得到,比如我們測試有遇到的數據越界問題:刪除購物車商品偶爾會造成程序閃退,但從黑盒測試上是不能必現的,只能從代碼層面去找問題

? 2. 邊界值行為特征錯誤:代碼在執行過程中發生異常,崩潰或者超時,此類錯誤通常發生在一些邊界條件上


邊界值特征錯誤.png

? 以上代碼就存在具有邊界行為特征錯誤。當b取值為0時,Division函數就會拋出運行時異常

? 3.經驗特征錯誤:根據過往經驗發現代碼錯誤


經驗特征錯誤.png

? 代碼想要表達的意思:如果變量i的值等于2,就調用函數operationA,否則調用函數operationB。

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

? 顯然,“if(i=2)”在語法上沒有錯誤,但是從過往經驗來看,這就很可能是個錯誤了

? 4、算法錯誤:代碼完成的計算(或則功能)和之前預先設計的計算結果(或則功能)不一致。
這類錯誤直接關系到代碼需要實現的業務邏輯,在整個代碼級測試中所占比重最大,也是最重要的。但是,完全的算法錯誤不常見,因為不能準備完成基本功能需求的代碼,是一定不會被提交的。所以,項目中最常見的是部分算法錯誤。

? 5、部分算法錯誤:在一些特定條件或則輸入情況下,算法不能準確完成業務要求實現的功能。


部分代碼錯誤.png

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

代碼級測試常用方法

代碼級測試常用方法.jpg

自動靜態方法能夠以極低的成本發現以下問題:

  • 使用未初始化的變量;
  • 變量在使用前未定義;
  • 變量聲明了但未使用;
  • 變量類型不匹配;
  • 部分內存泄漏的問題;
  • 空指針引用;
  • 緩沖區溢出;
  • 數組越界;
  • 不可達的僵尸代碼;
  • 過高的代碼復雜度;
  • 死循環;
  • 大量的重復代碼塊;
    ....
    實現方式:企業結合自己的編碼規范定制度規程庫,并于本地的IDE開發環境和持續集成流水線整合

代碼本地開發階段,IDE環境就可以自動對代碼實現自動靜態檢查;當代碼提交到倉庫后,CI/CD流水線自動觸發代碼靜態檢查,如果檢查到潛在錯誤,就會自動發郵件通知代碼遞交者。

? 如圖:C語言代碼存在數組越界的問題,通過C語言的自動靜態掃描工具splint發現這個問題,并給出分析結果。

靜態掃描結果.png

動態測試方法也就是單元測試方法,看似簡單,但在實際工程中會遇到很多困難:
? 1、單元測試用例”輸入參數“的復雜性,表現在”輸入參數“不是簡單的函數輸入參數。本質上,任何能夠影響代碼執行路徑的參數,都是被測函數的輸入參數。
? 2、單元測試用例”預期輸出“的復雜性,主要表現在”預期輸出“應該包括被測函數執行完成后所改寫的所有數據
? 3、關聯代碼不可用,需要采用樁代碼模擬不可用代碼,并通過打樁補齊未定義部分

單元測試用例 ”輸入參數“的復雜性
函數內部調用子函數獲得數據

函數內部調用子函數.png

? 函數 Func_SUT 是被測函數,它的內部調用了函數 FuncX,函數 FuncX 的返回值是 bool 類型,并賦值給內部變量toggle,之后代碼會根據變量toggle的取值來決定執行哪個代碼分支。那么,被測函數內部調用子函數獲得的數據也是單元測試的輸入參數。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 什么是單元測試 單元測試是軟件開發過程中的一種質量保證手段。最初的來源是想模仿對硬件芯片做單元測試那樣,在軟件中也...
    MagicBowen閱讀 22,288評論 0 18
  • Android單元測試介紹 處于高速迭代開發中的Android項目往往需要除黑盒測試外更加可靠的質量保障,這正是單...
    東經315度閱讀 3,148評論 6 37
  • -----轉載----- 1、問:你在測試中發現了一個bug,但是開發經理認為這不是一個bug,你應該怎樣解決? ...
    花開沉浮閱讀 7,448評論 4 88
  • 一.基本介紹 背景: 目前處于高速迭代開發中的Android項目往往需要除黑盒測試外更加可靠的質量保障,這正是單元...
    anmi7閱讀 2,057評論 0 6
  • 一、負載均衡高可用 Nginx作為負載均衡器,所有請求都到了Nginx,可見Nginx處于非常重點的位置,如果Ng...
    我只是一個小白木木閱讀 471評論 0 7