iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
AI Engineering

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

【Day28】補上那一格的,是一條 pytest.raises

  • 分享至 

  • xImage
  •  

TL;DR

  • 人寫的測試套組漏掉兩種錯法,原因不是斷言不夠嚴,是二十三組案例沒有一組餵那個輸入
  • 十份 AI 產物裡有一份補上了其中一格。補上它的不是蛻變關係,是一條要求拋出例外的防呆測試
  • 另一格從第一次量變異分數活到現在,十份、143 條、三十天,沒有任何一條碰得到

https://ithelp.ithome.com.tw/upload/images/20260928/20103826WtqAjumZU7.jpg


前言

第四幕問的是:不知道正確答案的時候,還測得動嗎?

今天結帳。把十份 AI 寫的蛻變測試放到那十四個變異體前面,看它們有沒有抓到人漏掉的。


人漏掉的那兩格

第一次量變異分數的時候,人手寫的那套測試殺掉 12 個,存活兩個:

✗ 存活 M09 金額負值校驗失效
✗ 存活 M14 退休後固定收入未隨通膨調升

今天重跑,數字一樣:85.7%,存活的還是這兩個。

當時的驗屍寫得很清楚,而且比「分數不夠高」難堪得多:

兩個存活的都不是「斷言不夠嚴」,是「案例沒餵那個輸入」。

M09 的變異是把金額負值的檢查整段換成 pass:

# 原本
if any(amt < 0 for amt in amounts):
    raise ValueError("金額不得為負數")

# 變異後
pass

那二十三組案例裡,沒有一組餵過負值。那個 if 執行了二十三次,條件恆為 False,raise 一次都沒進去。所以拿掉整段檢查,沒有任何一組會變。

M14 的情況更隱蔽:total_income += other_income * 12 * (1 + i)^k 那一行每一組都執行了,但二十三組的 other_income 全部是 0。零乘什麼都是零,拿掉通膨指數沒有任何一組會變。那一行被執行過,從來沒被檢驗過:這正是行覆蓋率那篇當時只能推論的事,現在有一個具名的變異體站在那裡。

更難堪的是,退休後收入要隨通膨調升這件事,規格考古那天就裁決過、寫進 PRD-10,後來還改過一次基準年。決定做了兩次,寫了兩次,然後沒有任何一組測試輸入讓那個欄位非零。

M09 是沒觸發例外,M14 是用零掩蓋了運算。兩格都不是斷言的問題,是資料根本沒把真正的邏輯逼出來。


有一份補上了 M09

十份產物逐條跑,只有一份碰到了那兩格之一。

run-B-MR-01.txt|關係 17 條|變異體 14 個

  test_mr_prd05_out_of_range_lump_sum_invariance   殺掉 2   M06、M07
  test_mr_prd09_surplus_income_ceiling_invariance  殺掉 1   M05
  test_mr_prd03_prd12_negative_balance_no_clipping 殺掉 1   M02
  test_mr_prd04_input_validation_negative_amount   殺掉 1   M09

計分 17 條|聯集殺掉 5 / 14(35.7%)

三個數字在這裡對上了:它單獨是 5/14,而人寫的那套已經有 12/14,兩邊聯集正好 13/14,也就是 92.9%,跟流水線當初量到的合計分數一致。它多出來的那一個,就是 M09。

而補上它的是這條:

# 把月投資金額輸入為負值;呼叫 calculate 必須拋出例外;
# 因為 PRD-04 規定金額必須非負且禁止靜默丟棄。
def test_mr_prd04_input_validation_negative_amount():
    p_negative = replace(make_base_params(), monthly_investment=-500.0)
    with pytest.raises(Exception):
        calculate(p_negative)

這不是蛻變關係。 它只執行一次,不比較兩次執行之間的任何東西。提示詞從頭到尾要的是蛻變測試,而真正補上人類缺口的那一條,是一條最土的 pytest.raises。

那十七條裡,13 條殺不掉這十四種錯法中的任何一個。會殺的四條,三條是蛻變關係(兩條不變量、一條單調性),殺的都是人寫的那套本來就殺得掉的;唯一補上人類缺口的那一條,不是蛻變關係。 至於齊次、疊加、對目標金額的單調性那些數學上最漂亮的,全部掛零,跟前幾天人手寫那四條關係的結果一樣。

另一格 M14,十份、142 條計分的關係,沒有任何一條碰到。跑之前就猜到了:前幾天寫關係的時候逐條推過,四類關係都碰不到它——今天只是兌現。


為什麼只有那一份

另外有一份也寫了校驗測試,但它沒碰到 M09。

run-A-MR-01.txt
  test_mr_input_validation   這批錯法碰不到

翻開看,它測的是年齡順序:現齡等於退休年齡時必須拋出例外。而 M09 改的是金額負值那一段,兩者不相干。

差別只在切法。那份補上 M09 的把同一條規格拆成兩個測試:

test_mr_prd04_input_validation_invalid_age_ordering   ← 年齡
test_mr_prd04_input_validation_negative_amount        ← 負值

另一份寫成一條,只寫了年齡。

掃過十份:

寫了校驗測試 其中測「金額負值」
十份產物 2 份 1 份

八份完全沒寫校驗測試。而提示詞要的是蛻變測試:所以那八份是照著指示做的,補上缺口的那一份,寫了指示沒有要求的東西。



放進工作

一、變異體活下來的時候,先問「有沒有餵過那個輸入」。

直覺會先怪斷言太鬆。今天這兩格都不是——從來沒有一組輸入把那段邏輯逼出來。

行覆蓋率在這裡只是半個答案。M09 的 raise 沒跑到,覆蓋率會標紅,但它標的是「沒跑到」,不是「沒測到」,中間還隔一步。M14 那行乘以零照樣算執行過,覆蓋率是綠的,什麼都看不出來。

二、要 AI 補測試的時候,「補什麼」比「寫得多好」重要。

那條補上缺口的測試,論精巧程度是十份裡最低的幾條之一。它值錢是因為它餵了一個沒有人餵過的輸入。八份寫了大量結構漂亮的關係,在這十四種錯法上一格都沒多補。

三、把同一條規格拆開寫。

那份補上 M09 的,只是把一條規格的兩個子句分成兩個測試。另一份寫成一條,就只覆蓋了其中一句。這個習慣不用聰明,只要不偷懶。


後記

草稿的骨架原本寫成「九份對人寫的測試毫無增益」,還加了一句「有點打臉第四幕」。數字確實是那樣,但那把尺不能這樣用:十四個變異體是我自己設計的目錄,殺不掉只說明錯法碰不到,不說明關係沒價值。三天前才更正過一次,又滑了。

老實說改完比較無趣。「九份都沒用」讀起來像個結論,「一份抓到一個」讀起來像個腳註。但前者是我沒有的東西。


這些數字不能拿來做什麼

  • 「殺不掉」不等於「沒價值」。 這把尺只能證實,不能證偽。十四個變異體是人設計的目錄,換一份目錄結論就換一個
  • 所以本篇沒有任何一句在評價那些關係的優劣,只記錄「哪一條確實抓到了什麼」
  • 「只有一份補上」是十份、一個受測物、同一家兩臺模型的結果,不是比率定律
  • 那十四種錯法有 8 個來自實測、6 個是推測,目錄本身就有立場
  • 143 條裡有 1 條在未變異的實作上就紅(年額對月額那條),依規矩不列入計分。「沒有任何一條碰得到 M14」說的是計分的 142 條
  • M14 從頭活到現在,只說明這幾種方法都沒碰過它,不說明它不可能被碰到

逐條結果、事前預測與判定分支在 manifest.yaml。


只帶走一件事
變異體活下來的時候,先別怪斷言太鬆。
去查有沒有任何一組輸入,真的把那一行逼出來過。


判斷 AI 有沒有補上缺口,用的仍然是人做的那份目錄。

M09 被補上了,是因為有人先把「金額負值」寫成一個變異體。

那些沒被寫進目錄的錯法呢?


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


上一篇
【Day27】誰來查那個檢查:把「兩邊是不是同一個東西」寫成工具
下一篇
【Day29】多生成五份,會得到五份一樣的盲區
系列文
AI 寫的測試,誰來測? 共 33 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言