第 3 / 9 部 待錄

進階TDD

《Clean Craftsmanship》Podcast 準備稿:進階 TDD.測試替身與不確定性原理

書名: 整潔的軟體工藝 Clean Craftsmanship(Disciplines, Standards, and Ethics) 作者: Robert C. Martin(Uncle Bob) 系列: Reading Uncle Bob(恩普拉氏) 公開標題建議: Reading Uncle Bob|Clean Craftsmanship.測試替身與 TDD 的測不準 (3/9) 涵蓋範圍: Part I 第3章 Advanced TDD(漸進推導、卡住怎麼辦、AAA/BDD、五種 Test Doubles、不確定性原理、London vs Chicago)

背景速覽

上一集講的是 TDD 的「為什麼」與基本節奏,這集進入「深水區」——當 TDD 遇到真實世界的麻煩時怎麼辦。內容很硬但很實用:怎麼用測試「逼出」演算法、卡住了怎麼脫困、所有測試共通的 AAA 結構、五種測試替身(Test Doubles)的家譜,以及最有哲學深度的「TDD 不確定性原理」與兩大學派之爭。這集是給已經知道 TDD 是什麼、想知道「真的做下去會撞到什麼牆」的人聽的。

一句話重點

TDD 不是淺層技能而是要練數月數年的深功夫——它的兩個核心難題是「怎麼用替身隔離難測的依賴」與「確定性和靈活性無法兼得」(你要求測試越確定,它就越脆弱;越靈活,就越測不準)。

值得討論的重點

1. 五種測試替身:一個漸進的家譜

這是本章最實用的乾貨。Test Doubles 的核心機制是多型——把外部依賴藏在介面後面,再用替身實作它。五種替身是一個層層加料的家譜:

  • Dummy:介面的空實作,什麼都不做、回 null/zero。用在「函式需要這個參數,但測試邏輯根本不在乎它」。
  • Stub:是會「回傳特定值」的 Dummy,用來驅動被測系統走特定路徑(例如永遠回傳 false 的 RejectingAuthenticator)。
  • Spy:是會「記住別人對它做了什麼」的 Stub(呼叫幾次、傳了什麼參數)。危險在於它把測試耦合到實作細節。
  • Mock:是「知道預期行為、能自己判斷測試通不通過」的 Spy——斷言被寫進替身裡。Uncle Bob 坦言個人不愛 Mock,因為它把間諜行為和斷言綁死。
  • Fake:另成一類,是個「模擬器」,實作了簡化版業務規則。問題是會越長越大,最後複雜到自己需要被測試。

講這段的訣竅是強調「這是一條遞增的線」:Dummy → Stub → Spy → Mock,每一層只多加一件事。聽眾記住這個結構,以後讀任何測試框架文件都不會被術語搞混。

2. AAA = BDD = 有限狀態機:三個名字、同一件事

所有測試的骨架都是 Arrange / Act / Assert(Bill Wake 提出)。Dan North 2003 年的 BDD 用 Given / When / Then——其實是同義詞。Uncle Bob 再往下挖一層:這三元組根本就是有限狀態機的狀態轉移(當前狀態 → 事件 → 下一狀態)。於是有了那句很有份量的話:「你寫的每個測試都是你正在建立的有限狀態機的一個狀態轉移;你的測試套件如果完整,就是那台狀態機本身。」這個視角把「寫一堆零散測試」重新框成「在一格一格描繪系統的行為空間」。

3. 不確定性原理:確定性與靈活性的二選一

本章最有哲學味的一段,用 sin(x) 當極端範例。你有兩種測法:

  • Value tests(給輸入值、查輸出值):靈活——換演算法(泰勒級數換成 CORDIC)測試照樣過——但永遠不確定有沒有漏掉某個會壞的輸入(要窮舉所有 double 得約 2 乘以 10 的 19 次方個測試)。
  • 用 Spy 深入檢查計算過程:確定性高——但你一換演算法,所有 spy 測試全壞。

結論就是「TDD 不確定性原理」:你對測試要求的確定性越高,它就越不靈活;要求越靈活,就越不確定。這不是 bug,是本質限制。它也正好對應到 London vs Chicago 兩大學派——London 派(Freeman/Pryce)愛 mock、重演算法、偏好確定性、Outside-In 設計;Chicago 派(Fowler/ThoughtWorks)少用 mock、重結果、偏好靈活、Inside-Out。Uncle Bob 站 Chicago,但主張兩者融合:跨架構邊界用 Spy(London 風),元件內部用 value test(Chicago 風)

注意事項

⚠️ 「Mock」這個詞在業界被嚴重濫用。 大多數人嘴上說的「mock」其實是 Stub 或 Spy,而且現代 mock 框架(Mockito、Jest mock、Moq)把這五種替身全糊成一團。講的時候要提醒聽眾:Uncle Bob 這套五分法是「概念分類」,不是「框架分類」——重點是搞懂「這個替身到底在扮演哪種角色」,而不是糾結框架叫它什麼。

⚠️ Spy 與 Mock 的最大實務風險是過度耦合到實作。一旦你斷言「這個方法被呼叫了剛好兩次、參數是 X」,你就把測試綁死在「現在怎麼做」而非「該做到什麼」——之後任何重構都會誤報失敗。這正是脆弱測試的主要來源(下一集會深入)。給聽眾的準則:能用 value test 就別用 spy;非用 spy 不可時,問自己這是不是跨了一道架構邊界

⚠️ London vs Chicago 別講成「兩個門派打架」。Uncle Bob 自己都說兩者並非對立、所有實踐者都混用。真正的 takeaway 是那個「用測試需求來決定架構邊界」的洞見——測得很痛,往往是邊界劃錯了。

專家補充

💡 「測試套件就是一台有限狀態機」這個視角,可以接到 property-based testing(如 QuickCheck、Hypothesis)。Value test 是手動挑幾個狀態轉移;property testing 則是讓工具隨機生成大量轉移、自動找反例——某種程度上是對「不確定性原理」的部分反擊:你雖然測不完所有輸入,但可以讓機器幫你測幾萬個,把「確定性」往上推一截。Uncle Bob 寫書時著墨不多,但這是很自然的現代延伸。

💡 「不確定性原理」這個命名是借物理學的修辭(海森堡測不準),嚴格說它不是同一回事。但它捕捉到一個真實且深刻的工程權衡:測試越貼近實作就越精準也越脆弱,越貼近行為就越穩健也越粗糙。這個權衡在每一個團隊的測試策略裡都會反覆出現——譬如「要不要寫 UI 端到端測試」本質上就是這道題。

💡 給聽眾的實務心法:測試金字塔(大量便宜的單元測試 / 少量昂貴的端到端測試)其實就是「不確定性原理」的工程化答案——用大量靈活的 value test 撐底,用少數確定性高的整合測試守關鍵邊界,而不是兩者都要、兩者都做不好。

討論問題

🎙️ 開場鉤子:「如果我給你一個 sin(x) 函式,你要寫幾個測試才敢說『它一定對』?答案是:你永遠不敢。這就是今天要聊的測不準。」

🎙️ 自問自答:「Dummy、Stub、Spy、Mock、Fake 到底差在哪?」——用「一條遞增的線」回答:每一個都只比前一個多做一件事。

🎙️ 拋給聽眾:「你寫的測試,是綁在『程式碼怎麼做』上,還是『程式碼該做到什麼』上?前者每次重構都會誤報,後者才是資產。」

🎙️ 帶走的一題:「翻出一個你維護起來很煩、動不動就壞的測試——看看它是不是用了太多 spy/mock,把實作細節釘死了。」

更大範圍關聯

  • Test Doubles 的正典:Gerard Meszaros《xUnit Test Patterns》(2007) 是五種替身的定義出處;Steve Freeman & Nat Pryce《Growing Object-Oriented Software, Guided by Tests》(GOOS) 是 London 學派的聖經。想深挖的聽眾指這兩本。
  • 學派之爭的延伸:Martin Fowler 的文章〈Mocks Aren’t Stubs〉是這場 London/Chicago 辯論的經典文本,比書講得更清楚。
  • 同系列接點:本章的「脆弱測試」「為測試需求調整架構邊界」會在下一集 Test Design 全面展開,是承上啟下的關鍵。
  • 領域定位:用 [軟體工程領域切分藍圖] 看,這章站在「測試策略」的核心——它回答的不是「怎麼寫一個測試」,而是「一整套測試該如何設計才不會反過來拖垮你」。

錄製建議

  • 建議時長:約 22-25 分鐘。配比:漸進推導與卡住脫困 4 分、AAA/BDD/狀態機 4 分、五種替身 8 分、不確定性原理 5 分、兩學派 4 分。
  • 討論策略:五種替身是這集的主菜,但純講術語會很乾——用「同一個 Authenticator 介面,五種不同的假貨」串起來講,每加一層就多一個能力。不確定性原理是這集的記憶點,值得放在後段當高潮,並順勢接到聽眾最有感的「測試金字塔」。提醒:這集偏硬,開場先講清楚「今天進深水區,沒聽過上一集的可以先補」。