「這個測試是綠的,代表這段邏輯是對的吧?」
Day 04 講過一個具體案例:AI 生成的測試全綠,卻沒測到任何有意義的行為。今天要把這件事系統化——假陽性測試不是單一一種毛病,而是三種不同的病灶,症狀都一樣(綠燈),但成因、辨識方法、修法完全不同。分不清楚是哪一種,就只能繼續靠直覺猜,猜不準。
最容易發現,也最容易被忽略的一種——測試裡確實有 assert,但斷言的內容弱到幾乎驗證不了任何事。
❌ 空斷言/斷言太弱:
result = calculateDiscount(order)
assert result is not null
→ 不管 calculateDiscount 算出多少、算法對不對,
只要沒有直接爆炸、回傳值不是 null,這個測試永遠綠燈
✅ 斷言具體的期望值:
result = calculateDiscount(order)
assert result == 150
→ 如果計算邏輯改壞了,算出的數字跟預期不同,
測試會立刻變紅
辨識方法很簡單:故意把待測邏輯改壞(例如把加法改成減法),如果測試還是綠燈,就代表斷言太弱。 這個檢驗法本身就是一種輕量級的「突變測試(mutation testing)」思維——不用真的跑突變測試工具,光是手動改壞一次、重跑一次測試,就能抓出這類問題。
比空斷言更隱蔽,因為表面上看起來斷言了具體的東西,但邏輯上這個斷言不可能為假。
❌ 恆真斷言:
result = validateAge(age)
assert result == true || result == false
→ 這個斷言邏輯上永遠成立,因為布林值不是 true 就是 false,
不管 validateAge 的邏輯對不對,這個斷言都通過
❌ 另一種常見樣貌:
items = getFilteredItems(criteria)
assert items.length >= 0
→ 陣列長度本來就不可能是負數,這個斷言永遠為真
恆真斷言之所以容易被 AI 寫出來,是因為它「看起來」符合一個測試該有的結構——有 Arrange、有 Act、有 Assert,語法完全正確,甚至斷言的變數名稱聽起來跟業務邏輯有關。但只看語法結構沒辦法分辨這件事,要真的讀懂斷言的邏輯內容,判斷它有沒有可能為假。
三種裡最危險的一種,因為它連「斷言」本身都可能寫得很扎實——問題出在斷言驗證的根本不是真實邏輯,而是被 mock 掉的行為。
❌ Mock 掉待測物件本身:
mockCalculator = mock(PriceCalculator)
mockCalculator.calculate(returns: 150)
service = OrderService(mockCalculator)
result = service.processOrder(order)
assert result.total == 150
→ 這個測試表面上在測 OrderService.processOrder,
但 150 這個數字是 mock 設定出來的,不是真的算出來的。
PriceCalculator 裡的計算邏輯不管改成什麼,這個測試都不會變
✅ 只 mock 真正需要隔離的外部依賴,核心邏輯留給真實物件驗證:
realCalculator = PriceCalculator()
service = OrderService(realCalculator)
result = service.processOrder(order)
assert result.total == 150
→ 150 是 PriceCalculator 真的算出來的,
計算邏輯改壞了,這個測試會反映出來
判斷該不該 mock 的關鍵問題是:「這個依賴是不是這次測試真正想驗證的核心邏輯」。 如果答案是「是」,就不該被 mock 掉;如果答案是「不是」(例如外部 API、資料庫、檔案系統),mock 掉才是正確做法。AI 容易搞混的地方是,它傾向對「所有依賴」一視同仁地 mock,而不會先問這個問題。
這三種樣貌有一個共同點:它們都能讓測試「看起來像一個測試」,語法上無可挑剔,執行結果是綠燈——這正是 AI 最容易被獎勵訊號誤導的地方。 如果評判「這個測試寫得好不好」的訊號只有「有沒有 assert」跟「有沒有通過」,AI 自然會往「用最省力的方式讓這兩個條件成立」收斂,而不是往「這個測試有沒有真的驗證到業務邏輯」收斂。
這也解釋了為什麼空斷言、恆真斷言、過度 mock 這三種假陽性,不是隨機出現的錯誤,而是同一個誘因(優化錯誤的訊號)在不同技術情境下的三種具體結果:空斷言是「懶得寫出具體期望值」、恆真斷言是「寫了看起來像斷言但邏輯上沒意義的東西」、過度 mock 是「隔離到連要測的東西本身都被隔離掉了」。
不管遇到哪一種假陽性,有一個共通的檢驗方法:拿到一段測試,先問「如果我故意把待測邏輯改壞(改錯運算、改反條件判斷、拿掉一段處理),這個測試會不會變紅」。 如果答案是「不會」,這個測試就是假陽性,不管它屬於三種樣貌裡的哪一種。
這個檢驗習慣值得養成,因為它比「讀懂每一行程式碼判斷寫得對不對」更省力,也更可靠——不用理解整段業務邏輯,只需要做一次思想實驗(或真的手動改壞一次),就能快速篩出可疑的測試。
回想你手上最近寫的(或 AI 幫你寫的)幾個測試,挑一個出來做今天的檢驗:故意把待測邏輯改壞,這個測試真的會變紅嗎?如果沒把握,那個測試的可信度,其實比你想的還低。
明天要進入 Red 階段:AI 在寫測試時,最容易跳過的一步不是寫測試本身,而是「先讓測試真的失敗過一次,確認它真的會抓到問題」——這一步被跳過時,前面講的三種假陽性,其實有一大半根本不會被發現。