Skip to content

manual-mutation-vs-stryker

AI 會自己把程式改壞來驗證測試,那還需要 Stryker 嗎?(゚д゚) ​

大家好,我是鱈魚。( ´ ▽ ` )ノ

之前介紹過突變測試,Stryker 會故意把程式改壞,看測試擋不擋得住。

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

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


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

那就來做個實驗看看,比比看用 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 逐筆驗過的嚴謹。(⌐■_■)✧

限制與完整數據都在實驗報告。

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