測試失敗了,應該修改程式,還是修改測試?

這個判斷決定了整個修復方向。判斷錯,我們會花時間做出一個「讓測試通過」但實際上破壞需求的修改。

核心原則:程式服務測試,測試服務需求

測試代表需求的具體描述。為了讓測試通過而修改測試本身,等於在需求上妥協。正確的做法是保持測試不變,調整程式實作直到符合期望。

唯一的例外:需求本身發生了變化——架構調整、業務流程重設計。這兩種情況的判斷,就是整個方法論的核心。

第一步:分類

面對測試失敗,先問「為什麼失敗」,不是「怎麼修」。

程式實作錯誤:需求沒變,但程式行為不符預期——錯誤輸出、邏輯判斷有誤、型別處理不當。處理方式直接:保持測試不變,修正程式。

最容易犯的錯誤,是在這種情況下改了測試的期望值,讓測試配合錯誤的程式。這等於讓錯誤行為成為「正確需求」。

架構變更需求:需求文件已更新,業務流程本質性改變,影響多個模組。這類情況確實需要調整測試,但前提是需求文件已反映這個變更。步驟:先確認文件、評估變更範圍、列出需修改的測試,最後才執行。跳過前置確認直接改測試,和無章法地亂改沒有區別。

觀測公開行為,不觀測內部實作

我們只驗證透過公開介面的輸入輸出、公開屬性的狀態變化。不該碰私有方法的調用順序、私有屬性的中間值。

一旦測試觀測內部狀態,它就和具體實作綁定了,任何重構都會讓測試失敗,即使行為沒變。

 1// 錯誤:觀測內部私有屬性
 2test('書籍驗證應該檢查所有欄位', () {
 3  final validator = BookValidator();
 4  validator.validate(invalidBook);
 5
 6  expect(validator._titleValidated, isTrue);   // 內部狀態
 7  expect(validator._isbnValidated, isTrue);    // 私有屬性
 8});
 9
10// 正確:觀測公開行為結果
11test('書籍驗證應該檢查所有欄位', () {
12  final validator = BookValidator();
13  final result = validator.validate(invalidBook);
14
15  expect(result.isValid, isFalse);
16  expect(result.errors, contains('標題不可為空'));
17  expect(result.errors, contains('ISBN 格式錯誤'));
18});

三個常見的反模式

測試遷就程式:程式返回錯誤值,開發者把測試期望值改成那個錯誤值。測試通過了,bug 也永遠存在了。正確做法:保持期望值不動,修正程式邏輯。

內部狀態依賴:測試直接存取 book._internalState 來驗證操作。一旦重構就失敗。改用公開方法 book.isAvailable() 驗證行為結果。

跳過文件檢查:覺得需求「應該」要改,就直接改測試,沒確認需求文件是否更新。正確做法:先查規格書,確認文件已反映變更,再評估影響範圍,最後才動手。

驗收標準

修復前:錯誤類型已判斷、需求文件狀態已確認、受影響測試範圍已識別。

修復後:所有測試 100% 通過、無內部狀態曝露、無行為旁路、每個修改都有測試保護。

100% 通過率是底線。某個測試通過不了,不是跳過它或修改它,是找出真正的問題。