iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0
AI Engineering

AI 寫的測試,誰來測?系列 第 22

【Day20】第一個變異分數 85.7%,而扣分的兩個都不是斷言的錯

  • 分享至 

  • xImage
  •  

TL;DR

  • 十四個領域變異體注進 calc_fixed.py,golden v2 套件殺掉十二個,變異分數 85.7%
  • 存活的 M09M14 都不是「斷言不夠嚴」,是案例沒餵那個輸入
  • 同一套測試:忠實性 100%、行覆蓋率 90.5%、變異分數 85.7%。三個數字指向的不是同一批問題

https://ithelp.ithome.com.tw/upload/images/20260920/201038260kYuI6hkQ8.jpg


前言

四天的工具在今天收斂:斷言檢核器、覆蓋率探針、過了五題校準的變異引擎,加上十四個帶著證據等級的變異體。

今天把三把尺同時架在同一套測試上,看它們各自說什麼。分數多少,跑完才知道。預期寫在 manifest.yaml 裡,跑之前就封存了。


85.7%

基底 shadow/calc_fixed.py,應戰的是 tests/test_golden_v2.py(23 組案例,21 組把數值鎖到 rel_tol = 1e-12,2 組要求拋 ValueError)。

基準線:未變異時 tests/test_golden_v2.py 全綠

  ✓ 殺掉 M01  折現指數 idx → idx + 1
  ✓ 殺掉 M02  餘額軌跡加回 max(0.0, ...)
  ✓ 殺掉 M03  名目月利率 → 有效月利率
  ✓ 殺掉 M04  本金退回年複利
  ✓ 殺掉 M05  移除淨支出截斷
  ✓ 殺掉 M06  提領期上界含大筆支出年齡
  ✓ 殺掉 M07  大筆支出配對 == → >=
  ✓ 殺掉 M08  移除年齡順序校驗
  ✗ 存活 M09  移除金額負值校驗
  ✓ 殺掉 M10  通膨基準年 −1
  ✓ 殺掉 M11  提領首年誤用退休前報酬率
  ✓ 殺掉 M12  起領年齡 >= → >
  ✓ 殺掉 M13  大筆支出不計通膨
  ✗ 存活 M14  退休後收入不隨通膨調升

殺掉 12 / 計分 14(ERROR 0、等價 0)
變異分數 = 85.7%

沒有無效變異體,沒有等價變異體,分母乾乾淨淨就是 14。

這個分數本身沒什麼好說的。先把另外兩把尺也架上去,存活的那兩個為什麼活著,答案會自己浮出來。


三個指標,看到三件不同的事

同一套 test_golden_v2.py

量什麼 結果
斷言忠實性(Day 16 檢核器) 100%,5 條精確斷言 + 1 條 pytest.raises 契約,閘門 3 PASS
行覆蓋率(Day 17 探針) 90.5%,42 行邏輯走過 38 行
變異分數(今天) 85.7%

三個數字,三個不同的視角。而它們指向的不是同一批問題。


兩個存活的,一個覆蓋率看得到,一個看不到

先看覆蓋率漏掉的那四行是什麼:

未覆蓋:73, 85, 89, 91

  73  raise ValueError("請領年齡不得為負數")
  85  raise ValueError("金額不得為負數")
  89  raise ValueError("大筆支出年齡不得為負數")
  91  raise ValueError("大筆支出金額不得為負數")

四行全部是「不得為負數」的 raise 那 9.5% 不是散落各處的邊角,是一整類校驗從來沒有被觸發過。

打開 golden_set_v2.json 數輸入,答案立刻出來:

檢查 結果
含負值輸入的案例 0 / 23
other_income 非零的案例 0 / 23

那兩組「預期被拒」的案例是 def_b_inverted(壽命 60 < 退休 65)與 def_b_equal(壽命 = 退休年齡),都是年齡順序違規,不是金額違規。所以 M09 拿掉的那段檢查,23 組裡沒有任何一組會踩到。

第 84 行的條件 if any(amt < 0 for amt in amounts) 有被執行,每次都是 False;第 85 行的 raise 一次都沒跑到。覆蓋率報表會把第 85 行標成紅的。

M14 是另一種處境。那一行是:

total_income += p.other_income * 12 * ((1.0 + p.inflation_rate) ** (t - p.current_age))

第 129 行在覆蓋率報表上是綠的:23 組每一組都走過它。但 other_income 全部是 0,0 × 12 × (1+i)^k0 × 12 恆等,拿掉通膨那一項,數字一位都不會動。

所以兩個存活的,剛好是覆蓋率的兩種處境:

覆蓋率看得到嗎 為什麼殺不掉
M09 看得到,第 85 行是紅的 那條路徑從來沒走過
M14 看不到,第 129 行是綠的 走過了,但乘數是 0

M09 那個洞,覆蓋率早就在報表上舉手了,只是沒有人去看那份報表。而 M14 那個洞,覆蓋率永遠不會舉手。


「執行過」與「被檢驗過」差在哪

Day 17 的結論是「行被執行過,不等於邏輯被檢驗過」。當時那是推論:覆蓋率滿分,變異體依然大量存活。今天 M14 是它的實證:第 129 行綠得漂亮,而改壞它沒有任何一組會紅。

要讓一行程式碼真的被檢驗,需要三件事同時成立:

  1. 控制流走到它(覆蓋率管這個)
  2. 餵進去的值要讓它的行為產生可觀察的差異
  3. 要有斷言去看那個差異

M09 卡在第一步,M14 卡在第二步。而忠實性 100% 證明第三步做得很好:它只是沒有機會發揮。

兩個存活的都不是被弱斷言放過的,是被輸入放過的。


順手掃出來的:golden v2 的輸入空間

五個欄位一起數完:

欄位 非零案例
other_income 0 / 23
annual_recurring_expense 0 / 23
labor_pension_monthly 1 / 23
labor_insurance_pension 4 / 23
lump_sums 6 / 23

第二列的 annual_recurring_expense 是 PRD-08 的固定年支出,Day 14 列的三條待補測試之一:那天寫下「會在分流之後一起寫」,六天過去,它還是 0 / 23。

這五列解釋了今天分數的組成,而且分界線異常乾淨:殺掉的十二個,改的路徑都至少有案例踩得到(折現與複利每一組都走,大筆支出有 6 組,勞保有 4 組,年齡校驗有 2 組非法輸入);存活的兩個,改的路徑是零組。


不補洞,先記著

現在往 golden set 塞兩組案例:一組負值金額、一組非零其他收入,分數會立刻變 100%。

我不打算這樣做,理由有三個。

「跑之前定,跑完不改」。 補完之後的 100% 是我為了好看調出來的,不是量出來的。

存活的變異體是產出,不是故障。 變異測試的功能就是指出盲區。M09M14 存活,意思是「這套測試從來沒餵過負值金額,也沒餵過非零的其他收入」,這正是花四天蓋工具想知道的事。把它修掉再宣布分數,等於摔了體溫計再說自己沒發燒。

golden set 的角色被搞混了。 它是「鎖住正確行為的快照」,不是「涵蓋所有輸入的測試設計」。要補這兩個洞,正確做法可能是另外寫針對性的測試,不是往快照裡塞案例。那是設計決定,該經過裁決,不該順手做。

所以今天的處置是記錄:兩個存活的都判為真漏洞、非等價,連同輸入空間那五列一起進 manifest.yaml


後記:AI 的預測紀錄

manifest.yaml 裡那欄 expectations 不是我寫的,是 Claude 寫的。跑之前封存,跑完不改。

它的預測 實際
M07M11M12 存活 全部被殺
M14 被殺 存活
M06 可能等價、M09 會被抓到 一個被殺、一個存活

十四個變異體,六個猜對、六個猜錯、兩個沒作答。

我把結果貼回去問它。它答得很漂亮:「我只看了變異體,沒看案例」,存活與否是改動與輸入共同決定,而它只算了一邊。承認得乾脆,但只挑兩個錯的認,另外四個隻字未提。接著在同一段寫下:

為什麼這樣還對了十二個⋯⋯

十二是被殺的變異體數量,不是它猜對的題數。它把受測物的成績抄成了自己的成績。

推理通順,認錯誠懇,甚至把盲區畫成表格,但它連自己錯了幾題都算不對。要接住這種錯,只能把預測封存,跑完親自對帳。那張對帳表是人做的。


這個 85.7% 不能拿來做什麼

它是「這套測試守得住我設計的十四種錯法裡的幾種」,不是「這套測試有多好」。分母換一份目錄,分數就換一個。

十四個變異體是我設計的,golden set 的 23 組是我定的,兩邊的盲區重疊:other_income 沒有變異體以外的測試看著,也沒有案例餵過它。這個分數量不到「我和 AI 都沒想到的那一類錯」。

單次量測,沒有分佈。今天沒有重跑的必要(變異注入是確定性的),但換一份測試套件、換一個基底,這個數字沒有可比性。

環境:Python 3.14.5、pytest 9.1.1。回傳碼的語意跟 pytest 大版本綁定,換版本要重新校準引擎。


只帶走一件事
一個變異體活下來,可能是斷言太鬆,也可能是那段程式碼從來沒被餵過會讓它出錯的值。
前者改測試,後者改案例,而分數不會告訴你是哪一種,得自己去驗屍。


今天的 repo:https://github.com/eyelash500/2026_ironman_test_ai/tree/day20


上一篇
【Day19】十四個變異體,八個有行號八個沒有
下一篇
【Day21】補完之後 100%,而那個從 Day 14 掛到現在的欄位還是 0
系列文
AI 寫的測試,誰來測?23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言