寫了 Unit Test 然後呢
一般來說談到要讓程式碼的品質變好,多數人想到的除了規範團隊的 Coding Style 、Code Review 之外,就屬撰寫 Unit test 來確保 production code 可以確實符合商業需求。
但是這邊就有一個問題產生了,究竟我要寫多少單元測試才夠呢?我的覆蓋率已經 100% 了,是否代表萬無一失了?但 100% 根本神話,而且這樣要寫超多單元測試,寫測試的時間都可以拿來做下一個需求了。。。
上述的問題在尚未引入 Mutation test 之前看起來是真的挺無解,也容易陷入覆蓋率的迷思之中。
Mutation Test 是什麼
Mutation test 說實話不是個新概念,他的歷史應該可以往回推到 1978 年的這篇論文。他的基本概念就是藉由改變 production code 來檢驗你的單元測試能否抓到這些改變,若能夠抓到就代表你的測試能夠確保目前的程式碼夠強健,能夠確保在商業邏輯改變後你可以知道什麼東西會跟以前不同。反之,若改變 production code 後單元測試卻沒有反應出來,就代表需要再補強單元測試。
會用到的必要 Packages
- junit official site
- org.pitest official site
junit 目前第五版也是可以使用 PIT,不過作法比較不同,需要引用不同的 Packages,可以參考這個來試試。
Demo Code
- source code:github repo
範例程式
以下這個是一個迴文函式,目的是用來判斷一個傳入的字串是否屬於迴文。
|
|
以下則是這個函式的測試程式
|
|
假設這是一個 Maven 的專案,那麼你可以執行以下指令來驗證測試是否通過
mvn clean
mvn compile
mvn test
PIT 基本設定
以下的設定都是基於假設目前是一個 Maven 專案來進行。
首先在 dependencies
這個區段中加入以下片段
|
|
接下來在 build
的 pluginManagement
的 plugins
區段中加入以下片段
|
|
這裡需要特別注意的是要指明 targetClasses
跟 targetTests
這兩個區塊內的設定是相當重要的,其中 targetClass
表示了 PIT 可以改變的範圍在哪裡,若打上星號的話,會將所有 packages 內的類別全數改一輪。而 targetTests
則是指明了,改變過後要跑得測試是在哪裡,而這也是它判定你的測試是否真有抓出變異的依據。
PIT 提供的變異種類
PIT 提供了非常多的變異可以使用,不過最常也最好用的就是一般預設的方式。而所有 PIT 提供的變異內容可以在這裡看到。
以下將只列出使用預設的運算子。
Conditionals Boundary Mutator
變更判斷式的上下界線。若原本是大於,則改成大於等於,若原本是小於等於,就改成小於。
Increments Mutor
累加變成累減 (i++ 改成 i–)
Invert Negatives Mutor
將返回的數值乘上 -1
Math Mutor
加減乘除改成相對的符號,像是加號變減號
Negate Conditionals Mutor
將判斷式做反轉,相等於改成不等於
Return Values Mutator
將回傳值改變,若為 value type 則會傳回相反的值 (ex. 返回 true 改成 false, 返回 int x 改成 x + 1), 若為 reference type 則返回 null。
Void Method Calls
將 void 函式移除
PIT 報表怎麼看
從範例程式抓下來若直接執行的話,應該可以看到如下的報表結果
若程式碼該行標記成紅色,代表有變異沒有被抓出來,而該行旁邊的 1, 2, 4 則表示該行進行多少次變異。
共有兩種結果會標記成紅色:
- 測試尚未覆蓋 (no coverage)
- 變異存活 (survived)
而若將範例程式被註解的測試打開後,在執行一次「編譯、測試、執行變異」再來看一次報表
則邊可以看到原本標記為 no converage
的已經沒有了,而且 mutation coverage 的分數也比上一次來的高,代表整體品質有所上升。至於其他的變異要怎麼消除提高分數,可以讓有興趣的人來嘗試一下。
Chinese Reference
- 變異測試 (Mutation Test) — 一種提高測試和代碼質量的 ”新” 方法速記
- Test - 變異(Mutation)測試之你的測試到底是寫爽的,還是有效的?
- 突變測試的測試模式 - Mutation Testing in Patterns