---
url: /blog-program/manual-mutation-vs-stryker.md
description: AI 寫測試時常會自己把程式改壞來驗證，這不就是手動版的突變測試嗎？來實驗看看與Stryker 有什麼差別。(ง •̀_•́)ง
---

![manual-mutation-vs-stryker](/manual-mutation-vs-stryker.webp){.cover}

# AI 會自己把程式改壞來驗證測試，那還需要 Stryker 嗎？(ﾟдﾟ)

大家好，我是鱈魚。%( ´ ▽ \` )ﾉ%

之前[介紹過突變測試](/blog-program/what-is-mutation-testing-and-stryker)，Stryker 會故意把程式改壞，看測試擋不擋得住。

最近發現 AI 會在寫完程式碼時，會手動自己程式改壞，跑測試看會不會紅，再改回來。

這聽起來不就是手動版的突變測試嗎？怎麼 AI 自己搶起 Stryker 的飯碗了！？%Σ(ˊДˋ;)%

\~~偏見~~直覺上我覺得讓 AI 手動改壞又慢又燒 token，不過感覺不能當證據。%( ・ิω・ิ)%

那就來做個實驗看看，比比看用 Stryker 和手動改壞各自寫出來的測試差多少。

## 這是什麼？能吃嗎？

還沒看過[突變測試介紹](/blog-program/what-is-mutation-testing-and-stryker)趕快去看，名詞我就不...，什麼？懶得去看？%(╥ω╥\`)%

好吧，先我們先快速認識名詞：%( ˘･з･)%

* **突變體：** Stryker 自動把程式改壞後產生的每個版本。
* **手動改壞：** 不用 Stryker，由 AI 自己決定改哪裡、怎麼改。
* **改壞版本：** AI 手動改壞時寫下的每個版本，作用跟突變體一樣。
* **擋住：** 改壞後跑測試，至少一條測試變紅或逾時。沒擋住也叫漏掉。
* **突變分數：** 擋住的突變體占全部突變體的比例。
* **等價：** 改了程式碼，行為卻完全沒變，任何測試都擋不住。

## 實驗怎麼做

實驗對象是我自己的專案 ttmatch-v2，一套桌球比賽紀錄系統（Nuxt、oRPC、Mongoose）。我挑了 5 個檔案，裡面有資料庫操作、資料驗證 schema 和權限判斷，比工具函式更接近日常業務程式。原本的測試全部刪掉，讓 AI 從頭寫。

受試者是 Claude Sonnet 5.5 子代理（Claude Code 派出去獨立工作的 subagent，推理強度 high），每組 3 位。

* **Stryker 組：** 只准用 Stryker，針對沒擋住的突變體補測試。
* **逐筆組：** 不准用 Stryker，也不准寫腳本。每次改壞都直接編輯原始碼，跑完測試立刻還原，改幾次由 AI 自己決定，最接近 AI 平常的做法。
* **免記錄組：** 不准用 Stryker，自己改壞程式、跑測試，沒變紅就補測試，可以直接編輯原始碼，也可以寫腳本批次改壞，不必記錄改壞版本。
* **不驗證組：** 只寫測試，完全不驗證，當作基準。

另外還有記錄組（做法和免記錄組相同，但每個改壞版本都要記錄）、並用組（先用 Stryker 補完，再手動改壞一輪）和交叉組（不驗證組寫完後，另一位審查者 AI 改壞程式來驗，撰寫者再依回報補測試）。

我用兩種方式評分。

* **評分一：** 拿 Stryker 的 885 個突變體算突變分數。Stryker 組補測試時就是對照這些突變體，所以這項評分對 Stryker 組有利。
* **評分二：** 記錄組和並用組寫下的改壞版本，去掉重複和有語法錯誤的版本後剩 924 個，再扣掉等價的剩 909 個。逐一套用到程式上，統計每套測試漏掉幾個。寫出這些版本的受試者稱為出題者。

所有受試者的測試都擋不住的版本，另外交給一位 Opus 子代理判斷是否等價。

## 發現一：用 Stryker 補的測試最嚴

| 組 | 平均突變分數 | 909 個改壞版本，三位各漏 |
|---|---|---|
| Stryker 組 | 97.48% | 21、32、26 個 |
| 免記錄組 | 91.37% | 41、35、39 個 |
| 逐筆組 | 90.55% | 49、53、58 個 |
| 不驗證組 | 90.40% | 68、52、61 個 |

Stryker 組在評分二照樣漏得最少，三位都比其他組任何一位少。

## 發現二：自己出題自己考，從此考試一百分 %(◐‿◑)%

逐筆組和不驗證組幾乎一樣，突變分數 90.55% 對 90.40%。

逐筆組三位各改壞 30 多次，自己的測試幾乎全擋住，所以幾乎沒補測試，兩位一條都沒補。少數沒擋住的，也都判成等價，其中 1 次確定判錯。

並用組先用 Stryker 補完，再手動改壞一輪，新增 15 條測試，突變分數完全沒變。這些測試擋得住自己想到的改壞版本（沒擋住的從 20 個降到 2 個），別人想到的卻只從 60 個降到 58 個。

AI 寫測試和改壞程式用的是同一套思路，寫測試時沒想到的情況，改壞時也想不到，等於球員兼裁判。%( ˘•ω•˘ )%

逐筆組花了 19.6 分鐘，不到 Stryker 組的一半，效果卻和只花 8.5 分鐘的不驗證組差不多。

簡單來說就是，逐筆改得很好，下次別改了。%(́⊙◞౪◟⊙‵)%

## 發現三：Stryker 和 AI 手動改壞各有盲點

用 Stryker 補出來的六套測試（Stryker 組，加上並用組手動改壞前），全部擋不住的改壞版本有 10 個，大多是在既有程式上多加一段轉換或限制，例如輸入多加 `trim()`、schema 多加 `.min(1)`。我推測原因在於 Stryker 內建的改壞規則只改寫或刪除既有程式，不會憑空加東西。

反過來，也有記錄組三位全漏、Stryker 組卻擋得住的改壞版本。

還有 1 個版本，所有受試者的測試都擋不住。敗場數的判斷條件從「自己在敗方」改成「對手在勝方」，兩位出題者都判成等價。實際上同一選手可以同時出現在勝方和敗方，這時敗場數會從 1 變成 0。

## 其他觀察

* **可以用腳本時，AI 全都寫腳本批次改壞。** 九位受試者一次都沒直接編輯原始碼，全部寫腳本批次套用，每位改壞一百多到五百多個版本，等於自製簡易版 Stryker。
* **要求記錄每個改壞版本，輸出 token 會變多。** 記錄組是 Stryker 組的 1.42 倍，免記錄組只有 1.07 倍。
* **換另一個 AI 來改壞，比自己改壞嚴格，但追不上 Stryker。** 交叉組突變分數 91.41%，接近免記錄組，花的時間卻和 Stryker 組差不多。不過審查者的提示詞和改壞次數都跟逐筆組不同，分不出是換人還是其他條件造成的效果。

## 總結 🐟

* **用 Stryker 補的測試最嚴。** 突變分數 97.48%，沒用 Stryker 的各組都在 90% 到 92%；拿改壞版本評分，Stryker 組也漏得最少。
* **AI 改壞的地方，都是自己測過的。** 逐筆改壞時，AI 挑的改法自己的測試幾乎都擋得住，嚴格程度和完全不驗證差不多；用 Stryker 補完後再手動改壞一輪，新增的測試也只擋得住自己想到的改法，突變分數沒變。
* **Stryker 和 AI 手動改壞各有盲點。** AI 會想到 Stryker 沒有的改法，例如多加 `trim()` 或 `.min(1)`；反過來也有 Stryker 組擋得住、AI 全漏的改壞版本。
* **可以用腳本時，AI 會自己寫一套簡易版 Stryker 批次改壞。**

從以上發現來看，建議預設用 Stryker，日常只跑改動的檔案，指令是 `npx stryker run --mutate <檔案>`。用 Stryker 比逐筆改壞多花一倍多的時間，多花的時間大多耗在 AI 等 Stryker 跑完；輸出 token 差不多，補出來的測試比較嚴。

真的沒辦法用 Stryker，就讓 AI 寫腳本大量改壞，或換另一個 AI 來改壞，補出來的測試都比 AI 逐筆驗過的嚴謹。%(⌐■\_■)✧%

限制與完整數據都在[實驗報告](https://github.com/Codfisher/cod-aquarium/blob/main/docs/manual-mutation-vs-stryker-experiment.md)。

有錯誤還請多多指教，感謝您讀到這裡，如果您覺得有收穫，歡迎分享出去。%( ´ ▽ \` )ﾉ%
