<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Solitary Unit Tests on Tarragon</title><link>https://tarrragon.github.io/blog/tags/solitary-unit-tests/</link><description>Recent content in Solitary Unit Tests on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 04 Mar 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/solitary-unit-tests/index.xml" rel="self" type="application/rss+xml"/><item><title>TDD 的兩種做法：邊界畫錯時看得出來的症狀</title><link>https://tarrragon.github.io/blog/record/behavior-first-tdd-methodology/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/record/behavior-first-tdd-methodology/</guid><description>&lt;p>測試的維護成本高於它提供的保護時，「還不如不寫」是一個理性的結論。而該修的通常是測試邊界畫的位置：邊界畫得越細，測試綁在程式內部結構上的地方越多，內部結構每動一次就要跟著改一次。&lt;/p>
&lt;p>TDD 的兩種做法差在這條邊界畫在哪一層。&lt;strong>Sociable&lt;/strong> 把它畫在模組的公開 API 上，&lt;strong>Solitary&lt;/strong> 畫在單一類別上。兩種定義的差別、以及判斷手上這套測試現在屬於哪一派的操作程序，在 &lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/unit-definition-two-schools/" data-link-title="Sociable vs Solitary Unit Test（單元的兩種定義）" data-link-desc="在「這個協作者該不該換成替身」上卡住、或讀到兩本測試書給出相反建議時，用來定位分歧的源頭">Sociable vs Solitary Unit Test&lt;/a> 那張卡。&lt;/p>
&lt;p>&lt;strong>這篇的射程是維護經濟性&lt;/strong>：它給的三個症狀量的都是「這套測試往後要花多少工」。這個選擇本身帶著立場——倫敦學派的核心主張是由外而內的設計發現，那件事不在這三個症狀的量測範圍內，本篇在 &lt;a href="#solitary-%e7%9a%84%e5%8f%a6%e4%b8%80%e9%a1%9e%e7%90%86%e7%94%b1">Solitary 的另一類理由&lt;/a>單獨處理它。只用維護成本這一個標準去衡量，答案會偏向 Sociable，而那有一部分是標準選出來的。本站多數內容預設 Sociable：理由（哪一邊的錯誤比較便宜修）在 &lt;a href="#%e6%89%8b%e4%b8%8a%e9%82%84%e6%b2%92%e6%9c%89%e6%b8%ac%e8%a9%a6%e7%9a%84%e6%99%82%e5%80%99">手上還沒有測試的時候&lt;/a>，代價（這個標準衡量不到的那件事）就在 Solitary 那一節。&lt;/p></description><content:encoded><![CDATA[<p>測試的維護成本高於它提供的保護時，「還不如不寫」是一個理性的結論。而該修的通常是測試邊界畫的位置：邊界畫得越細，測試綁在程式內部結構上的地方越多，內部結構每動一次就要跟著改一次。</p>
<p>TDD 的兩種做法差在這條邊界畫在哪一層。<strong>Sociable</strong> 把它畫在模組的公開 API 上，<strong>Solitary</strong> 畫在單一類別上。兩種定義的差別、以及判斷手上這套測試現在屬於哪一派的操作程序，在 <a href="/blog/testing/knowledge-cards/unit-definition-two-schools/" data-link-title="Sociable vs Solitary Unit Test（單元的兩種定義）" data-link-desc="在「這個協作者該不該換成替身」上卡住、或讀到兩本測試書給出相反建議時，用來定位分歧的源頭">Sociable vs Solitary Unit Test</a> 那張卡。</p>
<p><strong>這篇的射程是維護經濟性</strong>：它給的三個症狀量的都是「這套測試往後要花多少工」。這個選擇本身帶著立場——倫敦學派的核心主張是由外而內的設計發現，那件事不在這三個症狀的量測範圍內，本篇在 <a href="#solitary-%e7%9a%84%e5%8f%a6%e4%b8%80%e9%a1%9e%e7%90%86%e7%94%b1">Solitary 的另一類理由</a>單獨處理它。只用維護成本這一個標準去衡量，答案會偏向 Sociable，而那有一部分是標準選出來的。本站多數內容預設 Sociable：理由（哪一邊的錯誤比較便宜修）在 <a href="#%e6%89%8b%e4%b8%8a%e9%82%84%e6%b2%92%e6%9c%89%e6%b8%ac%e8%a9%a6%e7%9a%84%e6%99%82%e5%80%99">手上還沒有測試的時候</a>，代價（這個標準衡量不到的那件事）就在 Solitary 那一節。</p>
<h2 id="測試是可執行的需求規格">測試是可執行的需求規格</h2>
<p>兩種做法建立在同一個前置認知上：測試是<strong>用程式碼表達的需求規格</strong>。它的責任是說出系統該做什麼，證明實作寫對了只是這件事的副產品。</p>
<p>需求定義系統應該做什麼，實作是怎麼做的其中一種方式。需求保持穩定、實作隨時可換。Martin Fowler 在《Refactoring》給重構的定義正是這個分界：在不改變外部行為的前提下，調整程式的內部結構。</p>
<p>這個認知決定了測試該對什麼敏感。Kent Beck 在 <a href="https://testdesiderata.com/">Test Desiderata</a> 把它寫成兩條獨立的性質：<strong>behavioral</strong>（測試要對受測程式的行為改變敏感）與 <strong>structure-insensitive</strong>（程式結構改變時，測試的結果不應該跟著變）。兩種做法對第一條的答案相同，分歧全部落在第二條。Sociable 把它當成必須守住的性質，Solitary 把它當成可以交換的成本。</p>
<p>先寫測試比先寫程式再補測試快，這一條對兩種做法同樣成立，差別在設計問題暴露的時間點。紅燈、綠燈、重構的循環把「這個功能怎麼用、介面好不好用」的思考排在寫實作之前，於是難測的介面在寫測試的當下就現形：要先起一個容器才能建構、要塞五個參數才能呼叫、結果只能從 log 讀出來。Test-Last 的順序裡沒有人在寫測試，所以「難測」不產生任何訊號，那個設計問題要等到功能上線之後才由別的症狀暴露。</p>
<h2 id="邊界畫錯的症狀">邊界畫錯的症狀</h2>
<p>下面三個是最常撞到的，都要有一套跑著的測試才觀察得到——它們是診斷工具，不是選型公式。</p>
<p><strong>跑這個診斷的是維護那套測試的人</strong>，時機是做完一輪功能變更之後回頭看。要取三個數字：拿最近一次不改變外部行為的重構，數測試的改動量與生產程式碼的改動量（症狀一）；拿最近幾次紅燈，記從看到紅燈到指出哪裡出錯花了多久（症狀二）；跑一次完整測試，記時間（症狀三）。</p>
<p>三個症狀各自獨立，<strong>不必比大小、也不必決定先修哪一個</strong>：哪一個成立就照它的處方走，兩個同時成立就兩條都走。每個症狀的處方分兩層，第一層不移動邊界，第二層才移動；先試完第一層，再判斷要不要進第二層。</p>
<p>這份清單不封閉。同樣以維護成本為標準、而下面沒有展開的至少還有兩項：替身本身的製造成本（需要 codegen 步驟的語言比不需要的貴，沒有現成替身機制的語言更貴），以及模組粒度要涵蓋內部分支組合時的相乘成長。撞到它們時判讀方式相同——先問成本的兩個因子哪一個可以單獨降下來，再問移動邊界划不划算。</p>
<h3 id="症狀一責任一搬動測試就大批要改">症狀一：責任一搬動，測試就大批要改</h3>
<p><strong>觀察到什麼</strong>：某個計算從服務搬進實體、一個類別拆成兩個、幾個小類別併回去——外部行為完全沒變，而測試的改動量跟生產程式碼相當甚至更多。</p>
<p><strong>成因</strong>：這個成本是兩個數字的乘積，<strong>責任搬動的頻率 × 每次要重寫的替身設定量</strong>。業務規則持續演化的系統把第一個因子推高，而第二個因子由邊界位置決定：邊界畫在類別時，每個協作者各有一組替身設定，搬動一個方法，就要動到那個方法用到的每一個協作者的替身設定。</p>
<p>這個形態不會一次性壞掉，所以它常常沒有觀察者。每個變更只多花一點，而改測試的人往往就是改實作的人、在同一次提交內完成；成本要被看見，得有人固定去看測試改動與實作改動的比例——code review 會逐項看測試 diff 的團隊看得到，只看功能是否正確的團隊看不到。</p>
<p><strong>先試不移動邊界的解法</strong>：把替身設定集中到一個 test support 模組，讓責任搬動只改一處。第二個因子降下來之後，第一個因子再高也不痛。</p>
<p><strong>移動邊界才划算的條件</strong>：替身設定已經集中了、搬動仍然要改大量測試，代表測試斷言的就是協作結構本身。這時把邊界拉回模組。</p>
<h3 id="症狀二紅燈說不出哪裡壞">症狀二：紅燈說不出哪裡壞</h3>
<p><strong>觀察到什麼</strong>：測試變紅之後，找到出錯位置要花的時間長到讓人不想跑測試。</p>
<p><strong>成因</strong>：模組粒度的紅燈只說「輸出不對」，中間經過的步驟都是嫌疑犯。演算法密集的模組最容易撞到——數值計算、編解碼、加密系統的錯誤常常只在某個類別的邊界條件出現。</p>
<p><strong>先試不移動邊界的解法</strong>：定位時間同時受工具影響。斷言訊息把預期與實際的差異印清楚、中間狀態變成可觀察、property-based 測試的 shrinking 把失敗案例縮到最小——這幾件事都能把定位時間壓下來而不必動邊界。</p>
<p><strong>移動邊界才划算的條件</strong>：可觀察性補完之後定位仍然慢，而且模組的內部步驟數本來就多。逐類別隔離讓紅燈直接指到出錯的那一層，省下的偵錯時間可以超過症狀一的代價——前提是症狀一那個乘積本來就低，也就是那個模組的結構已經不太動了。</p>
<p>判斷結構穩不穩定，看規格是不是被外部凍結：已發布的協議版本、標準文件、對外契約算穩定。這一條有一個陷阱——設計還沒定案的人不會覺得自己沒定案，所以自評不算數，要拿得出外部憑據（凍結的版本號、簽過的契約、發布過的規格書）。拿不出來就當作還在動。</p>
<p>在結構還會動的模組上逐類別隔離，代價的形態是這樣：三個類別之間的介面一改，替身的方法簽名對不上，一批測試在實作還沒寫完之前就先紅了，而那些紅燈跟正確性無關。它不容易被判定成邊界畫錯，因為「改實作本來就要改測試」聽起來合理；而誤報與真實失敗共用同一個紅燈通道，兩者無法區分，於是那一批誤報要先全部修綠，才看得見真正的邊界條件測試有沒有壞。</p>
<h3 id="症狀三協作者拖慢或拖垮測試">症狀三：協作者拖慢或拖垮測試</h3>
<p><strong>觀察到什麼</strong>：整套測試跑完的時間長到沒有人在存檔時跑它，或者某些協作者根本建構不出來。</p>
<p><strong>成因分流</strong>：三種成因的處方不同。協作者本來就在外部世界（硬體、外部服務、需要真機才有意義的驅動層），那是應用邊界的替換、跟這個症狀無關，走 <a href="#sociable-%e6%80%8e%e9%ba%bc%e5%81%9a">Sociable 怎麼做</a>裡「把替換限制在應用邊界」那一件。造得起來但要二十行前置狀態，多半代表那個物件承擔了過多責任，它是設計訊號、正解是修設計；短期改不動時把它替換掉是合理的權宜，只是要知道自己在繞過什麼。造得起來、只是慢，才是這個症狀。</p>
<p>慢的份量要換算成尺度才看得出來：單個協作者多花三秒，兩百個測試就是十分鐘；到那個量級就沒有人在存檔時跑它了，回饋從「改一行看一次」退到「提交前跑一次」，而 Test-First 買到的時間點優勢正是在那裡失效的。這裡的三秒與兩百都是示例，換成自己的數字重算。</p>
<p><strong>先試不移動邊界的解法</strong>：只替換那一個慢的協作者，邊界留在模組。一個慢的依賴不必讓整個模組改用逐類別隔離。</p>
<p><strong>移動邊界才划算的條件</strong>：慢的協作者不只一個、而且散在模組各處，逐個替換等於已經在做 Solitary 了。</p>
<h2 id="手上還沒有測試的時候">手上還沒有測試的時候</h2>
<p>三個症狀都要有一套跑著的測試才觀察得到，新模組一個數字都沒有。這時的判斷不靠計算，靠哪一邊的錯誤比較便宜修。</p>
<p>預設 Sociable，理由是它的錯誤方向便宜：邊界畫得太粗、後來發現定位太慢，收縮是加測試，既有的測試留著仍然有效；邊界畫得太細、後來發現結構還在動，那些逐類別的測試要一批一批換成模組層的，而換的期間舊測試已經不可信、新測試還沒長齊，中間那段沒有回歸網。</p>
<p>重新評估的觸發點就是上面三個症狀任一出現。在那之前不必為了選型多做任何事。</p>
<h2 id="solitary-的另一類理由">Solitary 的另一類理由</h2>
<p>有一種選 Solitary 的理由跟維護成本無關，所以維護成本這個標準衡量不到它：<strong>用替身把還不存在的介面逼出來</strong>。從最外層往內開發時，每遇到一個尚未實作的協作者就先用替身頂替，於是替身在這裡的用途是設計工具而不是測試邊界的經濟權衡。這是 Steve Freeman 與 Nat Pryce 在《Growing Object-Oriented Software, Guided by Tests》主張的做法，也是倫敦學派的核心，而它主張的東西正是維護成本這個標準衡量不到的。跨團隊分工時同一個機制承擔契約佔位：雙方先固定介面，各自實作，替身是那份契約在測試裡的形體。</p>
<p>用途不同，所以它跟三個症狀不衝突也不被它們裁決。介面定案、對方實作出來之後，那些逐類別的測試要不要留下來，才輪到三個症狀裁決。</p>
<p>Solitary 的操作層本文不展開。由外而內的開發節奏、替身當設計手段怎麼落地，最完整的一次陳述在 Freeman 與 Pryce 那本。書單的 <a href="/blog/books/craft/verification/" data-link-title="驗證自己寫對了：軟體測試的選讀書目與各書的分歧" data-link-desc="測試驅動開發的原始定義、以 mock 為主的一派、評估測試的判斷標準、機器執行判斷標準之後人的工作，以及從規格推出測試案例的程序，分別由哪一本書討論，還有這幾本書彼此不同意的地方">驗證自己寫對了</a>在講那本的那一節寫明它的定位，以及它要求的前置經驗：先寫過一套自己後來覺得難維護的測試，否則這本的主張與批評它的主張聽起來都有道理。</p>
<h2 id="sociable-怎麼做">Sociable 怎麼做</h2>
<p>Sociable 的定義是把測試邊界畫在模組的公開 API 上，模組內部有幾個類別、它們怎麼協作，測試看不見也不斷言。這個定義落到操作層是三件事。</p>
<p><strong>找出模組的邊界</strong>。判斷標準是「這組類別對外承諾了什麼」，承諾的那個介面就是測試邊界，其餘都是實作細節。這個承諾要有機制撐著才存在——可見性修飾（package-private、internal）、套件結構、import 規則任一種都行；沒有任何機制擋住外部直接呼叫內部類別時，「邊界」只是一個口頭約定。邊界找錯的常見形態是把每個類別都當成一個模組，那會實際落成 Solitary 而寫的人不自知。</p>
<p><strong>讓 Domain 物件用真實實例</strong>。<a href="/blog/software-design/ddd/knowledge-cards/value-object/" data-link-title="Value Object" data-link-desc="判斷一個概念該用內容比對還是身份追蹤時使用。value object 的同一性由內容定義——內容相等就是同一個、替換實例對系統沒有影響。">值物件</a>、<a href="/blog/software-design/ddd/knowledge-cards/entity/" data-link-title="Entity" data-link-desc="判斷一個概念該建成 entity 還是 value object 時使用。entity 的同一性由身份定義——欄位全部改變、只要身份參照不變就是同一個。">實體</a>、純計算的協作者一律不替換——這些是不碰外部世界、只做計算與持有狀態的物件。造不起來時回到症狀三的分流。</p>
<p><strong>把替換限制在應用邊界</strong>。跨出本應用進程、需要外部世界回應的依賴才替換：資料庫、檔案系統、外部服務、時鐘、亂數。優先用 <a href="/blog/testing/knowledge-cards/stub/" data-link-title="Stub" data-link-desc="測試作者手動寫死回應資料的 test double：驗證的是假設成立時邏輯是否正確，假設本身錯誤時無法檢出">stub</a>（寫死回應）或 fake（有狀態的簡化實作）而不是 mock（斷言呼叫發生過），因為斷言的對象是最終狀態，不是呼叫過程。五種替身的分野在 <a href="/blog/testing/knowledge-cards/test-double-taxonomy/" data-link-title="Test Double Taxonomy（Fowler 分類）" data-link-desc="test double 依據回應資料的來源與驗證方式分成五種角色，各自對應不同的可測範圍與盲區">test double 分類</a>那張卡。</p>
<h2 id="跟-clean-architecture-的組合">跟 Clean Architecture 的組合</h2>
<p>Clean Architecture 跟 Sociable 建立在同一條前提上：業務邏輯獨立於外部世界。所以兩者組合起來沒有額外的對接成本，而且它的分層本身就是那個「撐住邊界的機制」。站內的實作路線在 <a href="../clean-architecture-implementation-methodology/">Clean Architecture 實作指引</a>。</p>
<p>Use Cases 層對外提供公開 API、對內只使用 Domain Entities、透過介面隔離外部依賴。這個結構直接給出上面三件事各自的答案：Use Case 的公開 API 是測試邊界，Domain Entities 用真實物件，要替換的只有 <a href="/blog/software-design/ddd/knowledge-cards/repository/" data-link-title="Repository" data-link-desc="查詢方法該留在 repository、還是該抽成獨立讀模型時使用。repository 是 aggregate 的存取抽象——回傳的形狀是 aggregate 的形狀，不是讀的形狀。">Repository</a> 這一層。</p>
<p>這個組合多買到一件事：對 Use Case 的單元測試同時就是業務驗收測試。一個名為「使用者提交訂單成功」的測試案例，不需要啟動 UI、不需要真實資料庫，驗證的卻是完整的業務流程。Alistair Cockburn 的 Hexagonal Architecture（又稱 Ports and Adapters，主張應用核心對外只透過抽象的埠溝通、UI 與資料庫都只是可替換的轉接器，構件層見 <a href="/blog/software-design/ddd/knowledge-cards/port/" data-link-title="Port" data-link-desc="判斷介面該宣告在哪一層、依賴方向該朝哪時使用。port 是 domain 對外宣告的介面——需求用領域語言說完、技術細節留在實作端。">port</a> 與 <a href="/blog/software-design/ddd/knowledge-cards/adapter/" data-link-title="Adapter" data-link-desc="把領域需求翻譯成具體技術操作的實作該放哪、跟領域的邊界在哪時使用。adapter 是 port 的具體實作——技術細節被擋在六角形之外的位置。">adapter</a>）把測試當成系統的另一種使用者，講的是同一個結構性質。</p>
<h2 id="行為快照落在兩種做法之外">行為快照落在兩種做法之外</h2>
<p>行為快照是替沒有測試的程式碼先架的一層保護：預期值直接取自程式當下的實際輸出，斷言的是現狀而非正確性。它不參加 Sociable 與 Solitary 的選邊，維護成本這個標準也評不動它：耦合結構在三個症狀的衡量裡是要被扣分的成本，對快照是工作方式本身。</p>
<p>它原本的定位是短期的入場資格：拆解依賴的期間先有回饋，正確性測試補上之後就移除。程式碼交給 agent 大幅改寫成為常態之後，這個定位的規模變了。每一次交出去的重構都需要先重新錄一份現有行為當保護，而那層保護的存續期間跟著重構的節奏走，不再是一次性的。</p>
<p>存續變長帶來一個要主動管理的責任：退場條件。同一段行為被正確性測試覆蓋之後，快照要移除。留著不移除的失效方式很安靜——行為改了、快照紅了，於是有人順手把快照更新成新行為，而正確性測試也綠著，兩份都綠而行為已經變了，「該相信哪一份」這個問題連問都不會被問到。可執行的退場觸發因此就是快照變紅那一刻：那時的預設處置是查有沒有正確性測試覆蓋同一段行為，有就刪掉快照，而不是更新它。完整判斷標準在 <a href="/blog/testing/knowledge-cards/characterization-test/" data-link-title="Characterization Test" data-link-desc="斷言「行為不變」而非「行為正確」的測試形態；與正確性測試的語意分界，決定紅燈能不能歸因">characterization test</a> 那張卡。</p>
<h2 id="bdd-是命名修正不是新方法">BDD 是命名修正，不是新方法</h2>
<p>Dan North 在 2006 年提出 <a href="https://dannorth.net/introducing-bdd/">BDD</a>（那篇原文交代這個詞被換掉的動機與最早的 Given-When-Then 用法），動機是修正「Test」這個詞造成的誤導：這個詞讓人以為要測試每個類別和方法，換成「Behavior」之後，意圖回到原位，測的是行為而不是程式結構。這跟 Kent Beck 2003 年在《Test Driven Development: By Example》示範的做法一致，換的是更難被誤解的詞。</p>
<p>《Software Engineering at Google》的測試章給了同一條規則，直接寫成小節標題：Test Behaviors, Not Methods。這個命名修正對應的正是 <a href="#%e6%b8%ac%e8%a9%a6%e6%98%af%e5%8f%af%e5%9f%b7%e8%a1%8c%e7%9a%84%e9%9c%80%e6%b1%82%e8%a6%8f%e6%a0%bc">測試是可執行的需求規格</a>那一節的 behavioral 性質；把它落到測試分層與 Given-When-Then 操作的一整篇，在 <a href="/blog/record/bdd-testing-methodology/" data-link-title="BDD 測試：測行為，不測實作細節" data-link-desc="替換一個實作就要跟著改掉大批測試、而業務邏輯根本沒動時，用來判斷哪一層該用 Given-When-Then、Mock 的邊界畫在哪裡">BDD 測試：測行為，不測實作細節</a>。</p>
<h2 id="這套診斷預設行為已經是對的">這套診斷預設行為已經是對的</h2>
<p>三個症狀問的都是「這套測試往後要花多少工」，而這個問題預設行為由寫程式的人決定：測試該耦合行為還是結構，前提是那個行為就是想要的行為。程式碼由 agent 產出時多出一個問題——行為由誰決定、憑什麼相信它是需求要的那一個。</p>
<p>這是它旁邊的另一個問題，不是同一個問題的延伸。本文的診斷在那個情境下照舊成立，而症狀一會更早出現：agent 重構的頻率與幅度都比人高，責任搬動的頻率那個因子被推得更高。「agent 可以順手把壞掉的測試修好」不構成反駁。測試在重構後被重新產生，意味著它重新對齊了新的實作，包含新引入的缺陷，修好的那一刻它作為回歸網（擋住既有行為被改壞的那一層測試）的功能就歸零。</p>
<p>改變的是這套診斷不再足夠。測試的預期值從哪裡來、憑什麼相信它，那一層由 <a href="/blog/testing/06-agent-authored-code/" data-link-title="模組六：Agent 產出程式碼的驗證" data-link-desc="程式碼由 agent 寫、人不再逐行讀時，用來決定驗證該落在哪些位置，以及每個位置的射程到哪裡">模組六：Agent 產出程式碼的驗證</a>承接。</p>
<p>Test-First 在同一個情境下也多了一個理由。<a href="#%e6%b8%ac%e8%a9%a6%e6%98%af%e5%8f%af%e5%9f%b7%e8%a1%8c%e7%9a%84%e9%9c%80%e6%b1%82%e8%a6%8f%e6%a0%bc">測試是可執行的需求規格</a>那一節給的理由是設計問題暴露的時間點，服務的是人的認知負荷。先寫測試在 agent 產出時另外製造出一件事：實作沒有可照抄的對象，於是預期值的來源與實作分開。判斷一組測試還剩多少驗證力，第一個問題就是它的預期值從哪裡長出來，<a href="/blog/testing/06-agent-authored-code/test-provenance-independence/" data-link-title="判斷標準的推導來源：測試與實作同源的形態、同源時抓得到的錯誤類別與拿回獨立性的變數" data-link-desc="出處同源與判斷標準被實作污染的差別，產出、組織與契約各層由誰決定判斷標準，以及不再逐行讀產出時由哪個環節接手驗證">判斷標準的推導來源</a>那一章處理這件事。</p>
<h2 id="這套診斷接到哪裡">這套診斷接到哪裡</h2>
<p>兩種定義的分野、耦合線怎麼數、以及判斷手上的測試屬於哪一派的操作程序，在 <a href="/blog/testing/knowledge-cards/unit-definition-two-schools/" data-link-title="Sociable vs Solitary Unit Test（單元的兩種定義）" data-link-desc="在「這個協作者該不該換成替身」上卡住、或讀到兩本測試書給出相反建議時，用來定位分歧的源頭">Sociable vs Solitary Unit Test</a> 那張卡。</p>
<p>兩派原著與對倫敦學派的系統性批評該讀哪本，走書單的 <a href="/blog/books/craft/verification/" data-link-title="驗證自己寫對了：軟體測試的選讀書目與各書的分歧" data-link-desc="測試驅動開發的原始定義、以 mock 為主的一派、評估測試的判斷標準、機器執行判斷標準之後人的工作，以及從規格推出測試案例的程序，分別由哪一本書討論，還有這幾本書彼此不同意的地方">驗證自己寫對了</a>：Kent Beck 的原始定義、Freeman 與 Pryce 的完整主張、Khorikov 的四支柱批評，各本的位置那篇有交代。Khorikov 那本給的是一組可以拿來替測試打分數的判斷標準，本篇的三個症狀只涵蓋其中「抵抗重構」與「快速回饋」兩項。</p>
<p>測試分層怎麼設計、協議整合怎麼驗證，走 <a href="/blog/testing/" data-link-title="開發測試實務指南" data-link-desc="測試全過而實機全壞、或程式碼由 agent 產出而無法逐行讀時，用來決定驗證該掛在哪幾層、每一層各自守不住什麼">Testing 測試策略</a>。跨應用邊界的替換該不該改用真實服務、斷言怎麼寫，走 <a href="/blog/testing/05-test-design-judgment/" data-link-title="模組五：測試設計判斷" data-link-desc="Mock 邊界判斷、assertion 設計、test data 代表性、flaky test 診斷">測試設計判斷</a>——那個模組判的是應用邊界外的層級差異，模組內協作者該不該替換是本文這三個症狀的事，兩者不同軸。沒有測試的既有程式碼要先取得入場資格（先讓改動有回饋、才動得了手），走書單的 <a href="/blog/books/craft/changing-existing-code/" data-link-title="改既有程式的選讀書目" data-link-desc="有測試保護時的重構目錄、沒有測試時的入場技術，以及小幅整理該現在做還是之後做的取捨，分別由哪一本書討論，還有各書的時效">改既有程式的選讀書目</a>。</p>
<hr>
<p>參考資料：</p>
<ul>
<li>Kent Beck，《Test Driven Development: By Example》，2003</li>
<li>Kent Beck，<a href="https://testdesiderata.com/">Test Desiderata</a>（各條性質的影片說明由他與 Kelly Sutton 合作）</li>
<li>Martin Fowler，《Refactoring: Improving the Design of Existing Code》，第二版 2018</li>
<li>Steve Freeman 與 Nat Pryce，《Growing Object-Oriented Software, Guided by Tests》，2009</li>
<li>Dan North，<a href="https://dannorth.net/introducing-bdd/">Introducing BDD</a>，2006</li>
<li>Google，《Software Engineering at Google》，2020</li>
<li>Valentina (Cupać) Jemuović，<a href="https://www.youtube.com/watch?v=3wxiQB2-m2k">TDD and Clean Architecture - Driven by Behaviour</a></li>
</ul>]]></content:encoded></item></channel></rss>