「昨天才講不能為了讓測試變綠就放寬斷言,今天卻要教 AI 寫一種『不管現在的行為對不對,先把它原封不動釘住』的測試?這不是自相矛盾嗎?」
第一次接觸「特徵測試」(characterization test)這個概念時,確實會有這種違和感。但這兩件事其實是同一個原則在不同情境下的應用——差別在於:Day 25 的情境是「已經知道正確答案是什麼,卻為了通過而放寬標準」;今天的情境是「根本不知道正確答案是什麼,得先把現況描述清楚,才有資格談對錯」。特徵測試釘住的是「現在的行為是什麼」,不是「現在的行為對不對」,這兩者不能混淆,混淆了才是真正的問題。
一般寫 TDD 測試時,斷言的預期值來自需求規格——你知道折扣該打幾折、金額該怎麼算,測試斷言的是「應該發生的事」。但遺留系統常常沒有這種規格可以依循:程式碼已經運作多年,原始需求文件早就不見了,甚至連當初寫這段邏輯的人都已經不在專案裡,唯一能確定的「事實」只剩下程式碼實際跑出來的結果。
特徵測試(characterization test)的做法是:不去猜測「這段程式碼應該怎麼運作」,而是直接執行它、觀察它實際輸出了什麼,把這個輸出原封不動地寫進斷言。 這件事聽起來簡單,實際上是遺留系統重構前最重要的一步——沒有這一步,任何後續改動都無從得知有沒有意外改變了行為。
這正是 AI 能發揮價值的地方:機械性地列出一段程式碼在各種輸入下的實際輸出,是一件需要耐心而不太需要業務判斷的工作,AI 可以快速跑過大量輸入組合、把結果整理成測試案例。這跟這個系列前面反覆強調的分工邏輯一致:把「觀察現況」這種可以被驗證的機械工作交給 AI,把「這個現況對不對」這種需要業務脈絡的判斷留給人。
用一組對照來看差異:
❌ 讓 AI 憑「看起來合理」的方式補測試:
「這段折扣計算邏輯沒有測試,幫我補上。」
→ AI 沒有規格可以依循,只能憑程式碼命名跟自己的理解猜測
「應該」要斷言什麼,猜錯了測試本身就是錯的
✅ 先要求 AI 老實記錄現況:
「這段折扣計算邏輯沒有測試,先幫我針對這些輸入組合,
執行現有程式碼、把實際輸出記錄成測試斷言,
不要猜測或修正任何看起來奇怪的地方。」
→ 測試斷言的是程式碼實際做了什麼,不是 AI 認為它該做什麼,
這份紀錄之後才能拿來當重構的安全網
特徵測試最危險的地方,是它完全不區分「正確的行為」跟「錯誤的行為」——只要是現在會發生的事,都會被原封不動地寫進斷言裡,包括那些其實是 bug 的行為。如果把特徵測試直接當成「這就是正確規格」寫進文件、或者往後每次改動都必須維持這份測試全綠,等於是把現有的 bug 用測試的形式正式化,變成往後誰都不敢動的「既定行為」。
這個陷阱特別容易在 AI 協作中被放大:AI 把特徵測試寫得又快又完整,測試報告看起來「這段程式碼已經有覆蓋了」,很容易讓人誤以為這代表「這段程式碼是對的」。事實上特徵測試只回答了「這段程式碼現在做了什麼」,完全沒有回答「這段程式碼應該做什麼」。這正是 Day 01 那句主題句的另一種樣貌——「已經有測試覆蓋」這個結論,只在「特徵測試」這個查證範圍內成立,不代表已經查證過「這是不是正確行為」。
正確的下一步,是拿著這份特徵測試去跟真正了解業務的人核對:這些行為裡,哪些是刻意設計、哪些其實是沒人發現的 bug。核對完才能決定,這份測試接下來該原封不動保留,還是該連著程式碼一起修正。
如果你手上也有一段沒有測試、沒有規格文件的遺留程式碼:你會先假設它現在的行為是對的,還是會先假設它可能藏著沒人發現的 bug?你打算怎麼確認哪些行為值得保留,哪些該被當成技術債處理?
明天要把這個系列前面 25 天累積的判斷準則收斂成一句話:品質把關不能外包給 AI 的三個判斷——這個測試該不該留、覆蓋夠不夠、重構要不要做。