測試分兩層:綁契約的是資產,綁實作的是拋棄式

「改一條規格要動 50 個測試」和「refactor 一次整片紅」是同一個病:把拋棄式的那層當共享資產在維護。

綁什麼數量改動頻率
契約層 / integration模組對外 API、DB schema、跨線 interface、錯誤碼與狀態機少而穩只有契約真的變才紅——共享資產
內部 unit單一 class 內部分支、演算法拋棄式:refactor 時連同實作刪掉重寫是正常的
  • AC ↔ test 的綁定只發生在契約層,那層本來就應該很少。想像「每個 unit test 都要對應一條業務場景」才是雞肋。
  • 內部 unit 層跟需求語言無關,它管的是設計回饋:難測 = 耦合過高。

unit / integration 的分界線

是否跨越一個我不能單方面改的邊界。

  • unit:一個決策點的邏輯正確性(分支、邊界、計算、錯誤處理),全在記憶體、不碰 IO ——驗「算得對不對」
  • integration:跨邊界的契約(真 DB、真 HTTP client、真 serialization)——驗「串起來對不對」

「測試變實作鏡像」反模式清單

  • mock 掉所有相依,只斷言「有呼叫 repo.save()」→ 驗的是程式碼長相,不是行為
  • 每個 private method 一個測試
  • 深比對整包物件,欄位一加就全紅
  • 測試名稱是方法名而非行為(testCalculate vs 過期訂單不計入折扣
  • setter / getter、純轉發層的覆蓋率填充測試

原則

測試綁行為與契約,不綁路徑——斷言看對外可觀察結果,不看內部呼叫順序、私有方法、mock 呼叫次數。另外,一條規格改動打到 50 個 unit,常常只是那 50 個測試各自重建同一套 fixture → 用 test data builder 把 setup 收斂到一處。

相關:AC 照業務規則切,不照使用情境切