<?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>Test-Provenance on Tarragon</title><link>https://tarrragon.github.io/blog/tags/test-provenance/</link><description>Recent content in Test-Provenance on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 24 Aug 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/test-provenance/index.xml" rel="self" type="application/rss+xml"/><item><title>判準的推導來源：測試憑什麼不是實作的回音</title><link>https://tarrragon.github.io/blog/testing/06-agent-authored-code/test-provenance-independence/</link><pubDate>Mon, 24 Aug 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/testing/06-agent-authored-code/test-provenance-independence/</guid><description>&lt;p>測試&lt;strong>抓出需求被誤讀&lt;/strong>的能力，取決於它的判準是不是由實作推導出來的。判準另有來源時，測試才有機會撞見實作對需求的誤解；判準從實作推導時，&lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/test-oracle/" data-link-title="Test Oracle（測試判準來源）" data-link-desc="一個測試憑什麼判定通過或失敗說不清楚、或被測對象沒有可逐例算出的正確答案時，用來定位判準的來源，以及每一種來源的射程">oracle&lt;/a> 在這一項上退化成實作自身的回音。其他幾種驗證力不受影響——崩潰、回歸、內部不一致照樣抓得到，下一節的表把界線畫出來。&lt;/p>
&lt;p>&lt;strong>出處是這條軸最常見的代理，不是這條軸本身。&lt;/strong> 同一次生成的測試與實作幾乎一定共用推導，所以出處同源通常意味著判準被污染；但兩者不是同一件事，而它們分岔的位置恰好是本模組推薦的做法之一——同一個 agent 同時產出實作與一條&lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/property-based-testing/" data-link-title="Property-Based Testing（性質式測試）" data-link-desc="逐例斷言列不完、或預期輸出難以逐例算出時，用來把驗證對象從個別例子換成對所有輸入都該成立的性質">往返性質&lt;/a>時，出處同源而判準來自需求，驗證力仍在。反過來也成立：兩個獨立會話，而寫測試那一側被餵了既有的 API schema，出處獨立而判準有一部分從實作推導。判定要問的是&lt;strong>這條斷言的預期值從哪裡長出來&lt;/strong>，出處只是最好查的那個線索。這條性質在測試由人手寫的年代很少被單獨討論，因為寫測試的人與寫實作的人即使是同一位，中間也隔著一段時間與一次重新理解需求的動作；產出速度提高之後那段間隔消失了，獨立性變成需要主動維持的性質。&lt;/p>
&lt;h2 id="判準被污染的三種形態">判準被污染的三種形態&lt;/h2>
&lt;p>判準被實作污染的形態由兩件事決定：&lt;strong>寫實作與寫斷言的推理是不是同一段&lt;/strong>，以及&lt;strong>寫斷言時手上有沒有實作可以照抄&lt;/strong>。兩者組合成四格，而第四格「不同推理、無可抄對象」是後面要談的做法（另一個來源先寫測試）、不是問題形態，所以不在這一節。以下三種按判準被污染的程度由重到輕排列。&lt;/p>
&lt;p>&lt;strong>同一次產出（同推理、無可抄對象）。&lt;/strong> 一個請求同時要求實作與測試，兩者由同一段推理產生。獨立性最低，因為需求的每一次誤讀都會被一致地寫進兩邊——這裡沒有可抄的實作，但也不需要抄，誤解在源頭就共用了。&lt;/p>
&lt;p>&lt;strong>實作完成後補測試（不同推理、有可抄對象）。&lt;/strong> 實作先存在，之後要求為它補一套測試。測試會照著實作的分支結構長出來，覆蓋率數字通常很高，而斷言的預期值多半是從實作推導的。推理雖然分了兩段，可抄的對象讓第二段推理沒有動力回到需求。&lt;/p>
&lt;p>&lt;strong>人讀完實作後補測試（不同推理、有可抄對象、但推理者換了）。&lt;/strong> 人讀過產出再寫測試，獨立性取決於讀的過程有沒有回到需求。讀完一份不熟悉的實作之後，腦中的「它該做什麼」已經被它實際做了什麼佔滿，寫出來的斷言常常是實作行為的複述。這一種比多數人以為的低，而它仍是三者中最高的——換了推理者本身就製造了一部分差異。&lt;/p>
&lt;p>三種形態共通的訊號是斷言的寫法：斷言重述實作的計算式（實作寫 &lt;code>total * 0.8&lt;/code>、斷言也算 &lt;code>total * 0.8&lt;/code>）。預期值只要能從實作推導出來，這條斷言就沒有在驗證任何東西。&lt;/p>
&lt;h2 id="判準被污染時還抓得到什麼">判準被污染時還抓得到什麼&lt;/h2>
&lt;p>同源不等於這組測試沒有價值，它的射程被限縮到特定幾類錯誤：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>錯誤類別&lt;/th>
 &lt;th>同源的測試抓不抓得到&lt;/th>
 &lt;th>原因&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>執行期崩潰、空指標、型別不符&lt;/td>
 &lt;td>抓得到&lt;/td>
 &lt;td>測試跑過那條路徑就會炸，與判準來源無關&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>後續改動造成的回歸&lt;/td>
 &lt;td>抓得到&lt;/td>
 &lt;td>基準是這次的行為，而基準只要穩定就能偵測變化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>實作內部不一致（兩處算法不同）&lt;/td>
 &lt;td>部分抓得到&lt;/td>
 &lt;td>兩處都被覆蓋且結果被比較時才會現形&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>需求被誤讀（順序、方向、邊界的語意）&lt;/td>
 &lt;td>抓不到&lt;/td>
 &lt;td>誤讀同時存在於實作與斷言，兩邊一致所以全綠&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>需求缺漏（沒有人想到的情境）&lt;/td>
 &lt;td>抓不到&lt;/td>
 &lt;td>沒有進入實作的情境也不會進入測試&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>最後兩類正是 agent 產出最容易出錯的地方，也是同源測試的系統性盲區。單據合併後明細 id 全部重建（下游持有舊 id 的取消與追加操作從此無聲失效，而畫面看起來一切正常）、危險判定的先後順序、幣別換算的方向、狀態機少一條轉換——這些都是需求語意層的問題，在實作與測試共用同一個誤解時全部通過。&lt;/p>
&lt;p>這個盲區不會因為測試變多而縮小。&lt;a href="https://tarrragon.github.io/blog/testing/cases/acceptance-passes-eight-different-programs/" data-link-title="T.C10 同一組驗收通過八個不同的程式 — 關卡放行的等價類有多大" data-link-desc="同一份需求重寫多次、產出的程式互不相同卻全部通過同一組驗收時，用來看清一組關卡實際固定住了哪些行為，又放過了哪些維度">T.C10&lt;/a> 裡八份程式的單元測試規模從零到 206 個範例，而 25 條驗收條件對危險判定順序一律沉默——規模成長沒有把任何一份帶進那個維度，因為斷言全部從同一份對需求的理解長出來。&lt;/p>
&lt;p>本節的三種形態與四個變數出自機制推導；用來對照的實驗數字只有一份來源，&lt;a href="https://tarrragon.github.io/blog/testing/06-agent-authored-code/" data-link-title="模組六：Agent 產出程式碼的驗證" data-link-desc="程式碼由 agent 寫、人不再逐行讀時，用來決定驗證該落在哪些位置，以及每個位置的射程到哪裡">模組入口&lt;/a>交代了它支持與不支持哪些宣稱。&lt;/p>
&lt;h2 id="把判準移出實作的四個變數">把判準移出實作的四個變數&lt;/h2>
&lt;p>獨立性可以被設計，能動的有四件事。前三件是本節展開的重點，第四件在開場已經出現過——&lt;strong>時間間隔&lt;/strong>：寫實作與寫測試之間隔一段時間，中間發生一次重新理解需求的動作。它在人手寫的年代是免費附贈的，產出速度提高之後要刻意安排，而它也是四者裡最不可靠的一個（間隔不保證有人真的回去讀需求）。&lt;/p>
&lt;p>&lt;strong>需求的來源。&lt;/strong> 把驗收條件寫成一份人審過的文件，測試的預期值從那份文件逐條取得。這是四者裡效果最強的一個，因為它直接把 oracle 的來源移到實作之外。這份文件自己會漂移，而漂移的方向剛好抵銷它的作用——實作改了之後有人回來把它「對齊現況」，判準就在維護環節被實作污染了。所以它要跟程式碼同 repo、同一個 PR 送審，而且變更要由不是實作者的人核可；做不到這一點時，依&lt;a href="https://tarrragon.github.io/blog/report/doc-sync-needs-mechanism-or-demotion/" data-link-title="多份文件必然漂移：同步期待要嘛有機制承接、要嘛明示降級" data-link-desc="設計開發流程的文件鏈（proposal、spec、UC、設計文件、追溯表、測試、註解）、或審查一套流程的文件模型時使用。判準是每份文件的同步期待與守護機制是否匹配：期待最新就要有機制守著、沒有機制就降級為一次性 scaffold 或 append-only 記錄。">同步期待要嘛有機制承接、要嘛明示降級&lt;/a>把它降級成標記消費時點的記錄，不要繼續當它是最新的。成本落在那份文件必須寫到可以逐條對應成斷言的程度——寫成「訂單要正確處理」沒有用，要寫到「同一個房間裡同時有蝙蝠與陷阱時，先觸發哪一個」這種顆粒度。條件顆粒度的設計在&lt;a href="https://tarrragon.github.io/blog/testing/06-agent-authored-code/acceptance-equivalence-class/" data-link-title="驗收條件的等價類：關卡實際固定住了什麼" data-link-desc="一組驗收條件通過就準備交付、卻說不出它放過了哪些行為時，用來攤出條件的維度、找沒被碰過的交叉並決定補哪一條">驗收條件的等價類&lt;/a>那章展開。&lt;/p>
&lt;p>&lt;strong>產出的順序。&lt;/strong> 測試先寫、實作後補，讓實作沒有可照抄的對象。這正是 Test-First 在 agent 產出情境下的新理由——它原本的理由是「小步好想、介面問題早暴露」，服務的是人的認知負荷；現在它多了一個結構性的理由，就是製造出處差異。順序帶來的獨立性有上限：先寫的測試如果也由同一個 agent 從同一段需求描述生成，誤讀仍然會一致地進入兩邊，順序只擋住了「照著實作寫斷言」這一種退化。&lt;/p>
&lt;p>&lt;strong>產出的主體。&lt;/strong> 測試與實作由不同的 agent、不同的會話、或不同的人產出，且寫測試的一方看不到實作。判準是&lt;strong>寫測試那一側看不到實作&lt;/strong>；「開兩個獨立的會話、只餵需求不餵程式碼」是寫作當下達成它的方式，而不是判準本身——跨會話記憶或共用專案 context 會讓兩個會話不再等於隔離，用之前先確認手上的工具是哪一種，成本是需求描述必須自足——寫測試那一側沒有實作可以參照，需求裡漏掉的部分就會直接變成測試的缺口。這個成本本身是有用的訊號，但它有一個判不開的地方：寫不出測試也可能是那一方缺領域上下文或經驗不足，同一個觀察對應得到兩個原因。要分開只有一種做法——同一份需求給兩個獨立的測試方，兩方卡在同一處才是需求的問題。&lt;/p>
&lt;p>&lt;strong>主體這條線往上還有兩層，而本模組其餘部分預設它們不存在。&lt;/strong> 「產出的主體」講的是 agent 與會話，那是最裡面那一層；外面還有組織層與契約層：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層&lt;/th>
 &lt;th>這一層的主體是誰&lt;/th>
 &lt;th>它決定什麼&lt;/th>
 &lt;th>不在同一方時會怎樣&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>產出&lt;/td>
 &lt;td>哪個 agent、哪次會話&lt;/td>
 &lt;td>判準與實作共不共用推導&lt;/td>
 &lt;td>本節四個變數處理的就是這一層&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>組織&lt;/td>
 &lt;td>寫需求的人、寫實作的人、驗收的人&lt;/td>
 &lt;td>誰有能力寫出可對應成斷言的條件&lt;/td>
 &lt;td>外包時驗收測試由乙方寫是同源的組織版；改由甲方寫則甲方多半沒有那個能力&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>契約與監理&lt;/td>
 &lt;td>客戶、主管機關、內稽&lt;/td>
 &lt;td>哪些閘門是外部義務、不由團隊決定&lt;/td>
 &lt;td>覆蓋率門檻寫進合約、或 code review 是變更管理控制項時，本模組的修法會與義務衝突&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>三層的判準相同（判準是不是由實作推導），可動的手段不同。本模組後面三章的修法都寫在產出層——&lt;strong>決定權、滿足義務、後果承擔屬於同一方&lt;/strong>是它們共同的前提。這個前提不成立時，先確認是哪一層的主體不同，再看那一層有沒有可動的變數；契約層通常沒有可動的變數，此時要動的是合約或控制程序本身，那超出本模組的射程——但&lt;strong>不代表沒有事情可做&lt;/strong>：外部門檻照舊守著、內部指標另外疊加上去，那條原則在&lt;a href="https://tarrragon.github.io/blog/testing/06-agent-authored-code/coverage-to-mutation-gate/" data-link-title="品質閘門的更替：覆蓋率、突變分數與各自的射程" data-link-desc="測試數量大幅增加而信心沒有跟著上升、或要為生成的測試挑一個不會被灌水的門檻時，用來決定量什麼，以及那個量的盲區在哪裡">品質閘門的更替&lt;/a>的導入順序段。&lt;/p>
&lt;p>四者可以疊加，也可以只動一件——但只動一件時要知道拿回了多少：動「需求的來源」把判準整個移出實作，語意層的盲區關得起來；只動順序或時間間隔只擋掉「照著實作寫斷言」這一種退化，共用誤讀那一種仍在，此時定位仍然是回歸網。都動不了的情況下，這組測試的定位要誠實降級成回歸網——它守得住「後來改壞了」，守不住「一開始就寫錯了」——並且在別處補上獨立的判準。&lt;/p>
&lt;h2 id="人審的位置要跟著移動">人審的位置要跟著移動&lt;/h2>
&lt;p>逐行讀 agent 寫的實作，讀的人已經被實作的敘事引導：變數命名、註解、結構都在說服讀者這段程式做的就是它該做的事，而讀者手上沒有一份獨立的對照。這種閱讀能發現的多半是風格與局部缺陷，發現不了「整段做的是另一件事」。&lt;/p>
&lt;p>同樣的注意力放在驗收條件上，審的是需求本身，而在需求、驗收條件、實作、測試這條鏈上，驗收條件是少數還沒有被實作污染的環節之一。這也是判斷「不再逐行讀產出」這個決定是否成立的方式：說得出哪一個環節接手了原本由閱讀承擔的那件事，這個決定就成立；說不出來時，交換掉的是驗證的可見性而不是閱讀的時間。&lt;/p>
&lt;h2 id="邊界判準取自實作也合理的場合">邊界：判準取自實作也合理的場合&lt;/h2>
&lt;p>一次性的探索腳本、用完就丟的資料轉換（「用完就丟」是對未來的預測，要說得出丟棄的時點或條件，說不出來的多半不會被丟）、以及&lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/characterization-test/" data-link-title="Characterization Test" data-link-desc="斷言「行為不變」而非「行為正確」的測試形態；與正確性測試的語意分界，決定紅燈能不能歸因">characterization test&lt;/a> 這種本來就以現狀為判準的測試，同源不構成問題——前兩者的錯誤代價低到不值得建立獨立判準，後者的設計意圖就是複製現狀。&lt;/p>
&lt;p>重構是另一種邊界，而且它的正確配置不在本節四個變數裡：重構的 oracle 是&lt;strong>改動前的行為&lt;/strong>而不是需求文件，所以「把驗收條件寫成人審過的文件」在這裡等於重建二十年的規格。可行的配置是拿舊實作當&lt;a href="https://tarrragon.github.io/blog/testing/06-agent-authored-code/oracle-beyond-examples/" data-link-title="判準寫不下來的時候：性質、變形關係與留給人的部分" data-link-desc="逐例預期值算不出來或跟不上產出速度時，用來決定判準退到哪一層、以及哪些驗證工作交不出去">參照 oracle&lt;/a>（新舊並行跑、比對輸出），配上&lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/characterization-test/" data-link-title="Characterization Test" data-link-desc="斷言「行為不變」而非「行為正確」的測試形態；與正確性測試的語意分界，決定紅燈能不能歸因">行為快照&lt;/a>守住尚未被正確性測試覆蓋的部分。&lt;/p>
&lt;p>另一個邊界是需求本身沒有語意深度的功能：CRUD 端點的欄位對映、單純的格式轉換，誤讀的空間小，同源測試的盲區也隨之縮小。判斷方式是問這個功能有沒有「兩件事同時發生時該怎麼辦」這類問題，沒有的話同源的風險確實低。&lt;/p>
&lt;h2 id="判讀訊號">判讀訊號&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>訊號&lt;/th>
 &lt;th>該做的事&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>斷言的預期值是實作算式的複述&lt;/td>
 &lt;td>回到需求文件取預期值；取不到就是需求沒寫到那個顆粒度&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>一次請求同時產出實作與測試&lt;/td>
 &lt;td>拆成兩次，並讓寫測試的那次看不到實作&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>覆蓋率數字很高而線上故障集中在語意層（順序、方向）&lt;/td>
 &lt;td>這是同源的典型指紋，補測試沒有用，要補的是獨立的驗收條件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>「我讀過了，測試也是照著需求寫的」&lt;/td>
 &lt;td>問寫測試的當下手上有沒有實作；有的話獨立性比想像中低&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>需求描述短到另一個人無法據以寫出測試&lt;/td>
 &lt;td>那份描述也不足以讓實作正確，先補需求再談測試&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>決定不再逐行讀產出&lt;/td>
 &lt;td>說出哪一個環節接手了原本由閱讀承擔的那件事，說不出來就先不要做這個決定&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>驗收條件要寫到什麼顆粒度才擋得住語意層錯誤 → &lt;a href="https://tarrragon.github.io/blog/testing/06-agent-authored-code/acceptance-equivalence-class/" data-link-title="驗收條件的等價類：關卡實際固定住了什麼" data-link-desc="一組驗收條件通過就準備交付、卻說不出它放過了哪些行為時，用來攤出條件的維度、找沒被碰過的交叉並決定補哪一條">驗收條件的等價類&lt;/a>&lt;/li>
&lt;li>有了獨立判準之後怎麼量測試的偵測能力 → &lt;a href="https://tarrragon.github.io/blog/testing/06-agent-authored-code/coverage-to-mutation-gate/" data-link-title="品質閘門的更替：覆蓋率、突變分數與各自的射程" data-link-desc="測試數量大幅增加而信心沒有跟著上升、或要為生成的測試挑一個不會被灌水的門檻時，用來決定量什麼，以及那個量的盲區在哪裡">品質閘門的更替&lt;/a>&lt;/li>
&lt;li>這條性質的術語卡 → &lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/test-provenance/" data-link-title="Test Provenance（測試出處）" data-link-desc="測試與被測實作由同一來源產出時（同一次生成、同一輪對話、同一個人同時寫），用來判斷這組測試還剩下多少驗證力">Test Provenance&lt;/a>&lt;/li>
&lt;li>由測試自己餵資料造成的同型盲區 → &lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/stub/" data-link-title="Stub" data-link-desc="測試作者手動寫死回應資料的 test double：驗證的是假設成立時邏輯是否正確，假設本身錯誤時無法檢出">Stub&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/testing/cases/stale-reference-stub-blindspot/" data-link-title="T.C5 凍結參照失效被 stub 遮蔽 — 測試全綠、功能全壞" data-link-desc="前端把後端資料的 id 凍結在本地記錄裡，後端的合併操作會重建資料、舊 id 全部失效 — 單元測試的 stub 由測試自己餵資料，永遠不會經歷「id 死亡」，bug 存在期間所有測試綠燈">T.C5&lt;/a>&lt;/li>
&lt;li>站內在測試耦合對象上的立場 → &lt;a href="https://tarrragon.github.io/blog/record/behavior-first-tdd-methodology/" data-link-title="行為優先的 TDD：測試耦合行為、不耦合結構" data-link-desc="重構時測試大量壞掉、或在「該不該 mock 這個協作者」上需要一個可執行的立場時，本站對兩派 TDD 分歧的選擇與推導">行為優先的 TDD&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>測試<strong>抓出需求被誤讀</strong>的能力，取決於它的判準是不是由實作推導出來的。判準另有來源時，測試才有機會撞見實作對需求的誤解；判準從實作推導時，<a href="/blog/testing/knowledge-cards/test-oracle/" data-link-title="Test Oracle（測試判準來源）" data-link-desc="一個測試憑什麼判定通過或失敗說不清楚、或被測對象沒有可逐例算出的正確答案時，用來定位判準的來源，以及每一種來源的射程">oracle</a> 在這一項上退化成實作自身的回音。其他幾種驗證力不受影響——崩潰、回歸、內部不一致照樣抓得到，下一節的表把界線畫出來。</p>
<p><strong>出處是這條軸最常見的代理，不是這條軸本身。</strong> 同一次生成的測試與實作幾乎一定共用推導，所以出處同源通常意味著判準被污染；但兩者不是同一件事，而它們分岔的位置恰好是本模組推薦的做法之一——同一個 agent 同時產出實作與一條<a href="/blog/testing/knowledge-cards/property-based-testing/" data-link-title="Property-Based Testing（性質式測試）" data-link-desc="逐例斷言列不完、或預期輸出難以逐例算出時，用來把驗證對象從個別例子換成對所有輸入都該成立的性質">往返性質</a>時，出處同源而判準來自需求，驗證力仍在。反過來也成立：兩個獨立會話，而寫測試那一側被餵了既有的 API schema，出處獨立而判準有一部分從實作推導。判定要問的是<strong>這條斷言的預期值從哪裡長出來</strong>，出處只是最好查的那個線索。這條性質在測試由人手寫的年代很少被單獨討論，因為寫測試的人與寫實作的人即使是同一位，中間也隔著一段時間與一次重新理解需求的動作；產出速度提高之後那段間隔消失了，獨立性變成需要主動維持的性質。</p>
<h2 id="判準被污染的三種形態">判準被污染的三種形態</h2>
<p>判準被實作污染的形態由兩件事決定：<strong>寫實作與寫斷言的推理是不是同一段</strong>，以及<strong>寫斷言時手上有沒有實作可以照抄</strong>。兩者組合成四格，而第四格「不同推理、無可抄對象」是後面要談的做法（另一個來源先寫測試）、不是問題形態，所以不在這一節。以下三種按判準被污染的程度由重到輕排列。</p>
<p><strong>同一次產出（同推理、無可抄對象）。</strong> 一個請求同時要求實作與測試，兩者由同一段推理產生。獨立性最低，因為需求的每一次誤讀都會被一致地寫進兩邊——這裡沒有可抄的實作，但也不需要抄，誤解在源頭就共用了。</p>
<p><strong>實作完成後補測試（不同推理、有可抄對象）。</strong> 實作先存在，之後要求為它補一套測試。測試會照著實作的分支結構長出來，覆蓋率數字通常很高，而斷言的預期值多半是從實作推導的。推理雖然分了兩段，可抄的對象讓第二段推理沒有動力回到需求。</p>
<p><strong>人讀完實作後補測試（不同推理、有可抄對象、但推理者換了）。</strong> 人讀過產出再寫測試，獨立性取決於讀的過程有沒有回到需求。讀完一份不熟悉的實作之後，腦中的「它該做什麼」已經被它實際做了什麼佔滿，寫出來的斷言常常是實作行為的複述。這一種比多數人以為的低，而它仍是三者中最高的——換了推理者本身就製造了一部分差異。</p>
<p>三種形態共通的訊號是斷言的寫法：斷言重述實作的計算式（實作寫 <code>total * 0.8</code>、斷言也算 <code>total * 0.8</code>）。預期值只要能從實作推導出來，這條斷言就沒有在驗證任何東西。</p>
<h2 id="判準被污染時還抓得到什麼">判準被污染時還抓得到什麼</h2>
<p>同源不等於這組測試沒有價值，它的射程被限縮到特定幾類錯誤：</p>
<table>
  <thead>
      <tr>
          <th>錯誤類別</th>
          <th>同源的測試抓不抓得到</th>
          <th>原因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>執行期崩潰、空指標、型別不符</td>
          <td>抓得到</td>
          <td>測試跑過那條路徑就會炸，與判準來源無關</td>
      </tr>
      <tr>
          <td>後續改動造成的回歸</td>
          <td>抓得到</td>
          <td>基準是這次的行為，而基準只要穩定就能偵測變化</td>
      </tr>
      <tr>
          <td>實作內部不一致（兩處算法不同）</td>
          <td>部分抓得到</td>
          <td>兩處都被覆蓋且結果被比較時才會現形</td>
      </tr>
      <tr>
          <td>需求被誤讀（順序、方向、邊界的語意）</td>
          <td>抓不到</td>
          <td>誤讀同時存在於實作與斷言，兩邊一致所以全綠</td>
      </tr>
      <tr>
          <td>需求缺漏（沒有人想到的情境）</td>
          <td>抓不到</td>
          <td>沒有進入實作的情境也不會進入測試</td>
      </tr>
  </tbody>
</table>
<p>最後兩類正是 agent 產出最容易出錯的地方，也是同源測試的系統性盲區。單據合併後明細 id 全部重建（下游持有舊 id 的取消與追加操作從此無聲失效，而畫面看起來一切正常）、危險判定的先後順序、幣別換算的方向、狀態機少一條轉換——這些都是需求語意層的問題，在實作與測試共用同一個誤解時全部通過。</p>
<p>這個盲區不會因為測試變多而縮小。<a href="/blog/testing/cases/acceptance-passes-eight-different-programs/" data-link-title="T.C10 同一組驗收通過八個不同的程式 — 關卡放行的等價類有多大" data-link-desc="同一份需求重寫多次、產出的程式互不相同卻全部通過同一組驗收時，用來看清一組關卡實際固定住了哪些行為，又放過了哪些維度">T.C10</a> 裡八份程式的單元測試規模從零到 206 個範例，而 25 條驗收條件對危險判定順序一律沉默——規模成長沒有把任何一份帶進那個維度，因為斷言全部從同一份對需求的理解長出來。</p>
<p>本節的三種形態與四個變數出自機制推導；用來對照的實驗數字只有一份來源，<a href="/blog/testing/06-agent-authored-code/" data-link-title="模組六：Agent 產出程式碼的驗證" data-link-desc="程式碼由 agent 寫、人不再逐行讀時，用來決定驗證該落在哪些位置，以及每個位置的射程到哪裡">模組入口</a>交代了它支持與不支持哪些宣稱。</p>
<h2 id="把判準移出實作的四個變數">把判準移出實作的四個變數</h2>
<p>獨立性可以被設計，能動的有四件事。前三件是本節展開的重點，第四件在開場已經出現過——<strong>時間間隔</strong>：寫實作與寫測試之間隔一段時間，中間發生一次重新理解需求的動作。它在人手寫的年代是免費附贈的，產出速度提高之後要刻意安排，而它也是四者裡最不可靠的一個（間隔不保證有人真的回去讀需求）。</p>
<p><strong>需求的來源。</strong> 把驗收條件寫成一份人審過的文件，測試的預期值從那份文件逐條取得。這是四者裡效果最強的一個，因為它直接把 oracle 的來源移到實作之外。這份文件自己會漂移，而漂移的方向剛好抵銷它的作用——實作改了之後有人回來把它「對齊現況」，判準就在維護環節被實作污染了。所以它要跟程式碼同 repo、同一個 PR 送審，而且變更要由不是實作者的人核可；做不到這一點時，依<a href="/blog/report/doc-sync-needs-mechanism-or-demotion/" data-link-title="多份文件必然漂移：同步期待要嘛有機制承接、要嘛明示降級" data-link-desc="設計開發流程的文件鏈（proposal、spec、UC、設計文件、追溯表、測試、註解）、或審查一套流程的文件模型時使用。判準是每份文件的同步期待與守護機制是否匹配：期待最新就要有機制守著、沒有機制就降級為一次性 scaffold 或 append-only 記錄。">同步期待要嘛有機制承接、要嘛明示降級</a>把它降級成標記消費時點的記錄，不要繼續當它是最新的。成本落在那份文件必須寫到可以逐條對應成斷言的程度——寫成「訂單要正確處理」沒有用，要寫到「同一個房間裡同時有蝙蝠與陷阱時，先觸發哪一個」這種顆粒度。條件顆粒度的設計在<a href="/blog/testing/06-agent-authored-code/acceptance-equivalence-class/" data-link-title="驗收條件的等價類：關卡實際固定住了什麼" data-link-desc="一組驗收條件通過就準備交付、卻說不出它放過了哪些行為時，用來攤出條件的維度、找沒被碰過的交叉並決定補哪一條">驗收條件的等價類</a>那章展開。</p>
<p><strong>產出的順序。</strong> 測試先寫、實作後補，讓實作沒有可照抄的對象。這正是 Test-First 在 agent 產出情境下的新理由——它原本的理由是「小步好想、介面問題早暴露」，服務的是人的認知負荷；現在它多了一個結構性的理由，就是製造出處差異。順序帶來的獨立性有上限：先寫的測試如果也由同一個 agent 從同一段需求描述生成，誤讀仍然會一致地進入兩邊，順序只擋住了「照著實作寫斷言」這一種退化。</p>
<p><strong>產出的主體。</strong> 測試與實作由不同的 agent、不同的會話、或不同的人產出，且寫測試的一方看不到實作。判準是<strong>寫測試那一側看不到實作</strong>；「開兩個獨立的會話、只餵需求不餵程式碼」是寫作當下達成它的方式，而不是判準本身——跨會話記憶或共用專案 context 會讓兩個會話不再等於隔離，用之前先確認手上的工具是哪一種，成本是需求描述必須自足——寫測試那一側沒有實作可以參照，需求裡漏掉的部分就會直接變成測試的缺口。這個成本本身是有用的訊號，但它有一個判不開的地方：寫不出測試也可能是那一方缺領域上下文或經驗不足，同一個觀察對應得到兩個原因。要分開只有一種做法——同一份需求給兩個獨立的測試方，兩方卡在同一處才是需求的問題。</p>
<p><strong>主體這條線往上還有兩層，而本模組其餘部分預設它們不存在。</strong> 「產出的主體」講的是 agent 與會話，那是最裡面那一層；外面還有組織層與契約層：</p>
<table>
  <thead>
      <tr>
          <th>層</th>
          <th>這一層的主體是誰</th>
          <th>它決定什麼</th>
          <th>不在同一方時會怎樣</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>產出</td>
          <td>哪個 agent、哪次會話</td>
          <td>判準與實作共不共用推導</td>
          <td>本節四個變數處理的就是這一層</td>
      </tr>
      <tr>
          <td>組織</td>
          <td>寫需求的人、寫實作的人、驗收的人</td>
          <td>誰有能力寫出可對應成斷言的條件</td>
          <td>外包時驗收測試由乙方寫是同源的組織版；改由甲方寫則甲方多半沒有那個能力</td>
      </tr>
      <tr>
          <td>契約與監理</td>
          <td>客戶、主管機關、內稽</td>
          <td>哪些閘門是外部義務、不由團隊決定</td>
          <td>覆蓋率門檻寫進合約、或 code review 是變更管理控制項時，本模組的修法會與義務衝突</td>
      </tr>
  </tbody>
</table>
<p>三層的判準相同（判準是不是由實作推導），可動的手段不同。本模組後面三章的修法都寫在產出層——<strong>決定權、滿足義務、後果承擔屬於同一方</strong>是它們共同的前提。這個前提不成立時，先確認是哪一層的主體不同，再看那一層有沒有可動的變數；契約層通常沒有可動的變數，此時要動的是合約或控制程序本身，那超出本模組的射程——但<strong>不代表沒有事情可做</strong>：外部門檻照舊守著、內部指標另外疊加上去，那條原則在<a href="/blog/testing/06-agent-authored-code/coverage-to-mutation-gate/" data-link-title="品質閘門的更替：覆蓋率、突變分數與各自的射程" data-link-desc="測試數量大幅增加而信心沒有跟著上升、或要為生成的測試挑一個不會被灌水的門檻時，用來決定量什麼，以及那個量的盲區在哪裡">品質閘門的更替</a>的導入順序段。</p>
<p>四者可以疊加，也可以只動一件——但只動一件時要知道拿回了多少：動「需求的來源」把判準整個移出實作，語意層的盲區關得起來；只動順序或時間間隔只擋掉「照著實作寫斷言」這一種退化，共用誤讀那一種仍在，此時定位仍然是回歸網。都動不了的情況下，這組測試的定位要誠實降級成回歸網——它守得住「後來改壞了」，守不住「一開始就寫錯了」——並且在別處補上獨立的判準。</p>
<h2 id="人審的位置要跟著移動">人審的位置要跟著移動</h2>
<p>逐行讀 agent 寫的實作，讀的人已經被實作的敘事引導：變數命名、註解、結構都在說服讀者這段程式做的就是它該做的事，而讀者手上沒有一份獨立的對照。這種閱讀能發現的多半是風格與局部缺陷，發現不了「整段做的是另一件事」。</p>
<p>同樣的注意力放在驗收條件上，審的是需求本身，而在需求、驗收條件、實作、測試這條鏈上，驗收條件是少數還沒有被實作污染的環節之一。這也是判斷「不再逐行讀產出」這個決定是否成立的方式：說得出哪一個環節接手了原本由閱讀承擔的那件事，這個決定就成立；說不出來時，交換掉的是驗證的可見性而不是閱讀的時間。</p>
<h2 id="邊界判準取自實作也合理的場合">邊界：判準取自實作也合理的場合</h2>
<p>一次性的探索腳本、用完就丟的資料轉換（「用完就丟」是對未來的預測，要說得出丟棄的時點或條件，說不出來的多半不會被丟）、以及<a href="/blog/testing/knowledge-cards/characterization-test/" data-link-title="Characterization Test" data-link-desc="斷言「行為不變」而非「行為正確」的測試形態；與正確性測試的語意分界，決定紅燈能不能歸因">characterization test</a> 這種本來就以現狀為判準的測試，同源不構成問題——前兩者的錯誤代價低到不值得建立獨立判準，後者的設計意圖就是複製現狀。</p>
<p>重構是另一種邊界，而且它的正確配置不在本節四個變數裡：重構的 oracle 是<strong>改動前的行為</strong>而不是需求文件，所以「把驗收條件寫成人審過的文件」在這裡等於重建二十年的規格。可行的配置是拿舊實作當<a href="/blog/testing/06-agent-authored-code/oracle-beyond-examples/" data-link-title="判準寫不下來的時候：性質、變形關係與留給人的部分" data-link-desc="逐例預期值算不出來或跟不上產出速度時，用來決定判準退到哪一層、以及哪些驗證工作交不出去">參照 oracle</a>（新舊並行跑、比對輸出），配上<a href="/blog/testing/knowledge-cards/characterization-test/" data-link-title="Characterization Test" data-link-desc="斷言「行為不變」而非「行為正確」的測試形態；與正確性測試的語意分界，決定紅燈能不能歸因">行為快照</a>守住尚未被正確性測試覆蓋的部分。</p>
<p>另一個邊界是需求本身沒有語意深度的功能：CRUD 端點的欄位對映、單純的格式轉換，誤讀的空間小，同源測試的盲區也隨之縮小。判斷方式是問這個功能有沒有「兩件事同時發生時該怎麼辦」這類問題，沒有的話同源的風險確實低。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>斷言的預期值是實作算式的複述</td>
          <td>回到需求文件取預期值；取不到就是需求沒寫到那個顆粒度</td>
      </tr>
      <tr>
          <td>一次請求同時產出實作與測試</td>
          <td>拆成兩次，並讓寫測試的那次看不到實作</td>
      </tr>
      <tr>
          <td>覆蓋率數字很高而線上故障集中在語意層（順序、方向）</td>
          <td>這是同源的典型指紋，補測試沒有用，要補的是獨立的驗收條件</td>
      </tr>
      <tr>
          <td>「我讀過了，測試也是照著需求寫的」</td>
          <td>問寫測試的當下手上有沒有實作；有的話獨立性比想像中低</td>
      </tr>
      <tr>
          <td>需求描述短到另一個人無法據以寫出測試</td>
          <td>那份描述也不足以讓實作正確，先補需求再談測試</td>
      </tr>
      <tr>
          <td>決定不再逐行讀產出</td>
          <td>說出哪一個環節接手了原本由閱讀承擔的那件事，說不出來就先不要做這個決定</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>驗收條件要寫到什麼顆粒度才擋得住語意層錯誤 → <a href="/blog/testing/06-agent-authored-code/acceptance-equivalence-class/" data-link-title="驗收條件的等價類：關卡實際固定住了什麼" data-link-desc="一組驗收條件通過就準備交付、卻說不出它放過了哪些行為時，用來攤出條件的維度、找沒被碰過的交叉並決定補哪一條">驗收條件的等價類</a></li>
<li>有了獨立判準之後怎麼量測試的偵測能力 → <a href="/blog/testing/06-agent-authored-code/coverage-to-mutation-gate/" data-link-title="品質閘門的更替：覆蓋率、突變分數與各自的射程" data-link-desc="測試數量大幅增加而信心沒有跟著上升、或要為生成的測試挑一個不會被灌水的門檻時，用來決定量什麼，以及那個量的盲區在哪裡">品質閘門的更替</a></li>
<li>這條性質的術語卡 → <a href="/blog/testing/knowledge-cards/test-provenance/" data-link-title="Test Provenance（測試出處）" data-link-desc="測試與被測實作由同一來源產出時（同一次生成、同一輪對話、同一個人同時寫），用來判斷這組測試還剩下多少驗證力">Test Provenance</a></li>
<li>由測試自己餵資料造成的同型盲區 → <a href="/blog/testing/knowledge-cards/stub/" data-link-title="Stub" data-link-desc="測試作者手動寫死回應資料的 test double：驗證的是假設成立時邏輯是否正確，假設本身錯誤時無法檢出">Stub</a> 與 <a href="/blog/testing/cases/stale-reference-stub-blindspot/" data-link-title="T.C5 凍結參照失效被 stub 遮蔽 — 測試全綠、功能全壞" data-link-desc="前端把後端資料的 id 凍結在本地記錄裡，後端的合併操作會重建資料、舊 id 全部失效 — 單元測試的 stub 由測試自己餵資料，永遠不會經歷「id 死亡」，bug 存在期間所有測試綠燈">T.C5</a></li>
<li>站內在測試耦合對象上的立場 → <a href="/blog/record/behavior-first-tdd-methodology/" data-link-title="行為優先的 TDD：測試耦合行為、不耦合結構" data-link-desc="重構時測試大量壞掉、或在「該不該 mock 這個協作者」上需要一個可執行的立場時，本站對兩派 TDD 分歧的選擇與推導">行為優先的 TDD</a></li>
</ul>
]]></content:encoded></item><item><title>Test Provenance（測試出處）</title><link>https://tarrragon.github.io/blog/testing/knowledge-cards/test-provenance/</link><pubDate>Mon, 24 Aug 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/testing/knowledge-cards/test-provenance/</guid><description>&lt;p>測試出處指的是測試與被測實作各自由誰、在什麼順序下產出。它是一項特定驗證力的&lt;strong>代理指標&lt;/strong>——抓出需求被誤讀的能力，真正取決於&lt;a href="https://tarrragon.github.io/blog/testing/06-agent-authored-code/test-provenance-independence/" data-link-title="判準的推導來源：測試憑什麼不是實作的回音" data-link-desc="測試與實作由同一次生成或同一輪對話產出時，用來判斷這組測試剩下多少驗證力，以及要動哪個變數才能拿回來">判準是不是由實作推導&lt;/a>出來的，而出處是這件事最好查的線索：同一次產出的兩者幾乎一定共用同一份對需求的理解，於是斷言編碼的是實作以為自己該做什麼，&lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/test-oracle/" data-link-title="Test Oracle（測試判準來源）" data-link-desc="一個測試憑什麼判定通過或失敗說不清楚、或被測對象沒有可逐例算出的正確答案時，用來定位判準的來源，以及每一種來源的射程">oracle&lt;/a> 退化成實作自身的回音。&lt;/p>
&lt;p>代理會在兩個位置失準，記住它們就知道什麼時候不能只看出處：同源產出而判準是一條從需求來的不變量時，驗證力仍在；獨立產出而寫測試那一方被餵了 API schema 時，判準仍有一部分是推導來的。這跟 &lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/stub/" data-link-title="Stub" data-link-desc="測試作者手動寫死回應資料的 test double：驗證的是假設成立時邏輯是否正確，假設本身錯誤時無法檢出">stub&lt;/a> 那張卡描述的失效是同一個機制在不同層級的形態——stub 是餵資料的人同時寫斷言，出處問題是寫實作的人同時寫斷言。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>出處是 &lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/test-oracle/" data-link-title="Test Oracle（測試判準來源）" data-link-desc="一個測試憑什麼判定通過或失敗說不清楚、或被測對象沒有可逐例算出的正確答案時，用來定位判準的來源，以及每一種來源的射程">oracle 來源&lt;/a>之外的第二個維度。同一個 oracle 類型可以有不同的出處：規格 oracle 由人寫規格、機器寫實作時獨立，由機器同時產出規格與實作時不獨立。測試三層（unit / &lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/protocol-integration-test/" data-link-title="Protocol Integration Test" data-link-desc="驗證程式碼和真實外部服務之間的協議互動是否正確的 test 層級">protocol integration&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/screen-state-test/" data-link-title="Screen State Test" data-link-desc="驗證使用者可見的畫面狀態覆蓋度和狀態間轉換完整性的 test 層級">screen state&lt;/a>）對這個維度不作區分——分層決定驗證發生在哪，出處決定驗證是不是在照鏡子。&lt;/p>
&lt;p>判準被污染的程度是連續的，不是有無：判準完全從實作推導時抓不到任何需求誤讀，判準有一部分從實作推導（例如生成器的參數、性質的前置條件抄自實作的守衛條件）時抓得到一部分。形態的分類、各自的射程、以及可動的四個變數在&lt;a href="https://tarrragon.github.io/blog/testing/06-agent-authored-code/test-provenance-independence/" data-link-title="判準的推導來源：測試憑什麼不是實作的回音" data-link-desc="測試與實作由同一次生成或同一輪對話產出時，用來判斷這組測試剩下多少驗證力，以及要動哪個變數才能拿回來">判準的推導來源&lt;/a>那一章，卡片這裡不重複一套鍵不同的分類。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;p>最直接的訊號是順序：測試在實作之後由同一個來源補上時，它多半是照著實作的分支寫出來的，覆蓋率數字會很高而斷言貼著實作結構。另一個訊號是斷言的寫法——斷言重述實作的計算式（實作寫 &lt;code>a * 0.8&lt;/code>、斷言也算 &lt;code>a * 0.8&lt;/code>）：預期值是從實作推導來的，需求文件裡的那個數字從頭到尾沒被引用。&lt;/p>
&lt;p>規格層的錯誤是這類測試的系統性盲區。危險處理順序寫反、狀態機少一個轉換、幣別換算方向顛倒——這些在實作與測試共用同一個誤解時全部通過，因為兩邊錯得一致。&lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/mutation-testing/" data-link-title="Mutation Testing（突變測試）" data-link-desc="行覆蓋率很高卻仍漏掉 bug、或要挑一個比覆蓋率難灌水的測試品質指標時，用來判斷這套測試實際擋得住什麼">mutation testing&lt;/a> 也發現不了它們，理由與突變的產生方式有關、在那張卡上。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>出處是可以被設計的，不是給定的：需求的來源、產出的順序、產出的主體、以及兩者之間的時間間隔，四者各自可動。四個變數的取捨、成本與操作方式在&lt;a href="https://tarrragon.github.io/blog/testing/06-agent-authored-code/test-provenance-independence/" data-link-title="判準的推導來源：測試憑什麼不是實作的回音" data-link-desc="測試與實作由同一次生成或同一輪對話產出時，用來判斷這組測試剩下多少驗證力，以及要動哪個變數才能拿回來">判準的推導來源&lt;/a>那一章展開。&lt;/p>
&lt;p>卡片層要記住的判準只有一條：四者都動不了時，這組測試的定位要降級成回歸網，並且在別處補上獨立的判準。&lt;/p></description><content:encoded><![CDATA[<p>測試出處指的是測試與被測實作各自由誰、在什麼順序下產出。它是一項特定驗證力的<strong>代理指標</strong>——抓出需求被誤讀的能力，真正取決於<a href="/blog/testing/06-agent-authored-code/test-provenance-independence/" data-link-title="判準的推導來源：測試憑什麼不是實作的回音" data-link-desc="測試與實作由同一次生成或同一輪對話產出時，用來判斷這組測試剩下多少驗證力，以及要動哪個變數才能拿回來">判準是不是由實作推導</a>出來的，而出處是這件事最好查的線索：同一次產出的兩者幾乎一定共用同一份對需求的理解，於是斷言編碼的是實作以為自己該做什麼，<a href="/blog/testing/knowledge-cards/test-oracle/" data-link-title="Test Oracle（測試判準來源）" data-link-desc="一個測試憑什麼判定通過或失敗說不清楚、或被測對象沒有可逐例算出的正確答案時，用來定位判準的來源，以及每一種來源的射程">oracle</a> 退化成實作自身的回音。</p>
<p>代理會在兩個位置失準，記住它們就知道什麼時候不能只看出處：同源產出而判準是一條從需求來的不變量時，驗證力仍在；獨立產出而寫測試那一方被餵了 API schema 時，判準仍有一部分是推導來的。這跟 <a href="/blog/testing/knowledge-cards/stub/" data-link-title="Stub" data-link-desc="測試作者手動寫死回應資料的 test double：驗證的是假設成立時邏輯是否正確，假設本身錯誤時無法檢出">stub</a> 那張卡描述的失效是同一個機制在不同層級的形態——stub 是餵資料的人同時寫斷言，出處問題是寫實作的人同時寫斷言。</p>
<h2 id="概念位置">概念位置</h2>
<p>出處是 <a href="/blog/testing/knowledge-cards/test-oracle/" data-link-title="Test Oracle（測試判準來源）" data-link-desc="一個測試憑什麼判定通過或失敗說不清楚、或被測對象沒有可逐例算出的正確答案時，用來定位判準的來源，以及每一種來源的射程">oracle 來源</a>之外的第二個維度。同一個 oracle 類型可以有不同的出處：規格 oracle 由人寫規格、機器寫實作時獨立，由機器同時產出規格與實作時不獨立。測試三層（unit / <a href="/blog/testing/knowledge-cards/protocol-integration-test/" data-link-title="Protocol Integration Test" data-link-desc="驗證程式碼和真實外部服務之間的協議互動是否正確的 test 層級">protocol integration</a> / <a href="/blog/testing/knowledge-cards/screen-state-test/" data-link-title="Screen State Test" data-link-desc="驗證使用者可見的畫面狀態覆蓋度和狀態間轉換完整性的 test 層級">screen state</a>）對這個維度不作區分——分層決定驗證發生在哪，出處決定驗證是不是在照鏡子。</p>
<p>判準被污染的程度是連續的，不是有無：判準完全從實作推導時抓不到任何需求誤讀，判準有一部分從實作推導（例如生成器的參數、性質的前置條件抄自實作的守衛條件）時抓得到一部分。形態的分類、各自的射程、以及可動的四個變數在<a href="/blog/testing/06-agent-authored-code/test-provenance-independence/" data-link-title="判準的推導來源：測試憑什麼不是實作的回音" data-link-desc="測試與實作由同一次生成或同一輪對話產出時，用來判斷這組測試剩下多少驗證力，以及要動哪個變數才能拿回來">判準的推導來源</a>那一章，卡片這裡不重複一套鍵不同的分類。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<p>最直接的訊號是順序：測試在實作之後由同一個來源補上時，它多半是照著實作的分支寫出來的，覆蓋率數字會很高而斷言貼著實作結構。另一個訊號是斷言的寫法——斷言重述實作的計算式（實作寫 <code>a * 0.8</code>、斷言也算 <code>a * 0.8</code>）：預期值是從實作推導來的，需求文件裡的那個數字從頭到尾沒被引用。</p>
<p>規格層的錯誤是這類測試的系統性盲區。危險處理順序寫反、狀態機少一個轉換、幣別換算方向顛倒——這些在實作與測試共用同一個誤解時全部通過，因為兩邊錯得一致。<a href="/blog/testing/knowledge-cards/mutation-testing/" data-link-title="Mutation Testing（突變測試）" data-link-desc="行覆蓋率很高卻仍漏掉 bug、或要挑一個比覆蓋率難灌水的測試品質指標時，用來判斷這套測試實際擋得住什麼">mutation testing</a> 也發現不了它們，理由與突變的產生方式有關、在那張卡上。</p>
<h2 id="設計責任">設計責任</h2>
<p>出處是可以被設計的，不是給定的：需求的來源、產出的順序、產出的主體、以及兩者之間的時間間隔，四者各自可動。四個變數的取捨、成本與操作方式在<a href="/blog/testing/06-agent-authored-code/test-provenance-independence/" data-link-title="判準的推導來源：測試憑什麼不是實作的回音" data-link-desc="測試與實作由同一次生成或同一輪對話產出時，用來判斷這組測試剩下多少驗證力，以及要動哪個變數才能拿回來">判準的推導來源</a>那一章展開。</p>
<p>卡片層要記住的判準只有一條：四者都動不了時，這組測試的定位要降級成回歸網，並且在別處補上獨立的判準。</p>
]]></content:encoded></item></channel></rss>