錯誤修復和重構方法論
測試失敗了,應該修改程式,還是修改測試?
這個判斷決定了整個修復方向。判斷錯,我們會花時間做出一個「讓測試通過」但實際上破壞需求的修改。
核心原則:程式服務測試,測試服務需求
測試代表需求的具體描述。為了讓測試通過而修改測試本身,等於在需求上妥協。正確的做法是保持測試不變,調整程式實作直到符合期望。
唯一的例外:需求本身發生了變化——架構調整、業務流程重設計。這兩種情況的判斷,就是整個方法論的核心。
第一步:分類
面對測試失敗,先問「為什麼失敗」,不是「怎麼修」。
程式實作錯誤:需求沒變,但程式行為不符預期——錯誤輸出、邏輯判斷有誤、型別處理不當。處理方式直接:保持測試不變,修正程式。
最容易犯的錯誤,是在這種情況下改了測試的期望值,讓測試配合錯誤的程式。這等於讓錯誤行為成為「正確需求」。
架構變更需求:需求文件已更新,業務流程本質性改變,影響多個模組。這類情況確實需要調整測試,但前提是需求文件已反映這個變更。步驟:先確認文件、評估變更範圍、列出需修改的測試,最後才執行。跳過前置確認直接改測試,和無章法地亂改沒有區別。
觀測公開行為,不觀測內部實作
我們只驗證透過公開介面的輸入輸出、公開屬性的狀態變化。不該碰私有方法的調用順序、私有屬性的中間值。
一旦測試觀測內部狀態,它就和具體實作綁定了,任何重構都會讓測試失敗,即使行為沒變。
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% 通過率是底線。某個測試通過不了,不是跳過它或修改它,是找出真正的問題。