<?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>Ordering on Tarragon</title><link>https://tarrragon.github.io/blog/tags/ordering/</link><description>Recent content in Ordering on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 17 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/ordering/index.xml" rel="self" type="application/rss+xml"/><item><title>T.C6 流程測試首跑抓到修復自己引入的順序 bug</title><link>https://tarrragon.github.io/blog/testing/cases/flow-test-first-run-ordering-catch/</link><pubDate>Fri, 17 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/testing/cases/flow-test-first-run-ordering-catch/</guid><description>&lt;p>修復跨服務互動 bug 後，新建的&lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/flow-test/" data-link-title="Flow Test（流程測試）" data-link-desc="在假後端上驅動真實前端服務鏈、斷言散佈於業務旅程各階段的測試形態；與 unit / integration / E2E 的邊界劃分">流程測試&lt;/a>首跑就抓到修復自己引入的順序錯誤——單元測試綠燈、流程測試紅燈，紅燈才是對的。單元測試為了聚焦而繞過的路徑，正是跨服務互動 bug 藏身的地方。前情：&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;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>這個系統的前端以輪詢讀取後端的事件流，把新事件轉成列印輸出；後端的「合併兩張單據」操作會重建明細、更換全部明細 id。&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> 的修復針對後者：合併後前端要「立即刷新一次快照」，讓後續操作拿到重建後的新明細 id。修復者把刷新放在「同步單據列表」之前。&lt;/p>
&lt;p>這個順序踩中輪詢的兩道機制。第一道是過濾：多店環境下，後端事件流混有其他店的資料，前端以本地單據列表為準濾掉非本店的部分。合併後的瞬間，本地列表還停在舊單據——新單據的資料被&lt;strong>整批濾掉&lt;/strong>，刷新等於白做。第二道是差異比對：輪詢以上一輪結果為基準，只把新增的部分觸發輸出；空結果覆寫了這個基準，下一輪輪詢會把整張單據誤判為新增而重複觸發列印。&lt;/p>
&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;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>「直接塞狀態」是單元測試的合理手段，也是它的邊界&lt;/strong>。單元測試驗證「取消邏輯在快照正確時是否正確」，把快照塞進去是聚焦的做法。但「快照如何變成正確的」——輪詢、過濾、差異比對這條鏈——不在它的視野裡。順序 bug 剛好住在這條鏈上。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>修復引入的 bug 比原始 bug 更難察覺&lt;/strong>。原始 bug 有使用者回報的症狀；修復引入的順序錯誤沒有立即症狀（要等到下一輪輪詢才重複列印），若沒有流程測試，它會以「偶發重複列印」的形態在生產環境出現，極難回溯。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>首跑就紅是價值不是意外&lt;/strong>。流程測試的建置成本花在假後端與服務鏈的組裝上；一旦組起來，它驗證的是「多個服務對同一份資料的接力是否正確」。單元測試在結構上放掉的正是這段接力。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>狀態的「來路」也要被測到&lt;/strong>。若被測邏輯依賴某份狀態，至少要有一條測試讓狀態走真實的產生路徑（輪詢、過濾、同步），而不是全部直接塞。&lt;/li>
&lt;li>&lt;strong>編排順序是一種契約&lt;/strong>。「A 必須先於 B」的約束要同時存在於兩處：編排程式碼的註解（說明不可對調的原因）與流程測試的劇本（順序錯了就紅）。&lt;/li>
&lt;li>&lt;strong>修復也要過流程測試&lt;/strong>。針對跨服務互動的修復，合入前先在流程測試上跑一輪——修復引入的迴歸與原始 bug 同樣真實。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>流程測試的地基怎麼搭 → &lt;a href="https://tarrragon.github.io/blog/testing/01-test-strategy-layers/semantic-fake-backend/" data-link-title="語意級假後端與流程測試" data-link-desc="bug 的成因是對後端行為的假設錯誤、由測試餵資料的 stub 驗證不出來時：建一個持有狀態、模擬已證實後端行為的假後端（test double 分類的 fake），讓流程測試走完整的多服務互動鏈">語意級假後端與流程測試&lt;/a>&lt;/li>
&lt;li>被繞過的鏈路屬於哪一層職責 → &lt;a href="https://tarrragon.github.io/blog/testing/01-test-strategy-layers/three-layer-definition/" data-link-title="三層定義與職責表" data-link-desc="Unit Test / Protocol Integration Test / Screen State Test 各層職責、驗證目標與盲區的完整論述">三層定義與職責表&lt;/a>&lt;/li>
&lt;li>前一集：bug 為什麼漏掉 → &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 凍結參照失效被 stub 遮蔽&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>修復跨服務互動 bug 後，新建的<a href="/blog/testing/knowledge-cards/flow-test/" data-link-title="Flow Test（流程測試）" data-link-desc="在假後端上驅動真實前端服務鏈、斷言散佈於業務旅程各階段的測試形態；與 unit / integration / E2E 的邊界劃分">流程測試</a>首跑就抓到修復自己引入的順序錯誤——單元測試綠燈、流程測試紅燈，紅燈才是對的。單元測試為了聚焦而繞過的路徑，正是跨服務互動 bug 藏身的地方。前情：<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> 的修復合入後觸發了此問題。</p>
<h2 id="觀察">觀察</h2>
<p>這個系統的前端以輪詢讀取後端的事件流，把新事件轉成列印輸出；後端的「合併兩張單據」操作會重建明細、更換全部明細 id。<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> 的修復針對後者：合併後前端要「立即刷新一次快照」，讓後續操作拿到重建後的新明細 id。修復者把刷新放在「同步單據列表」之前。</p>
<p>這個順序踩中輪詢的兩道機制。第一道是過濾：多店環境下，後端事件流混有其他店的資料，前端以本地單據列表為準濾掉非本店的部分。合併後的瞬間，本地列表還停在舊單據——新單據的資料被<strong>整批濾掉</strong>，刷新等於白做。第二道是差異比對：輪詢以上一輪結果為基準，只把新增的部分觸發輸出；空結果覆寫了這個基準，下一輪輪詢會把整張單據誤判為新增而重複觸發列印。</p>
<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>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>「直接塞狀態」是單元測試的合理手段，也是它的邊界</strong>。單元測試驗證「取消邏輯在快照正確時是否正確」，把快照塞進去是聚焦的做法。但「快照如何變成正確的」——輪詢、過濾、差異比對這條鏈——不在它的視野裡。順序 bug 剛好住在這條鏈上。</p>
</li>
<li>
<p><strong>修復引入的 bug 比原始 bug 更難察覺</strong>。原始 bug 有使用者回報的症狀；修復引入的順序錯誤沒有立即症狀（要等到下一輪輪詢才重複列印），若沒有流程測試，它會以「偶發重複列印」的形態在生產環境出現，極難回溯。</p>
</li>
<li>
<p><strong>首跑就紅是價值不是意外</strong>。流程測試的建置成本花在假後端與服務鏈的組裝上；一旦組起來，它驗證的是「多個服務對同一份資料的接力是否正確」。單元測試在結構上放掉的正是這段接力。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li><strong>狀態的「來路」也要被測到</strong>。若被測邏輯依賴某份狀態，至少要有一條測試讓狀態走真實的產生路徑（輪詢、過濾、同步），而不是全部直接塞。</li>
<li><strong>編排順序是一種契約</strong>。「A 必須先於 B」的約束要同時存在於兩處：編排程式碼的註解（說明不可對調的原因）與流程測試的劇本（順序錯了就紅）。</li>
<li><strong>修復也要過流程測試</strong>。針對跨服務互動的修復，合入前先在流程測試上跑一輪——修復引入的迴歸與原始 bug 同樣真實。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>流程測試的地基怎麼搭 → <a href="/blog/testing/01-test-strategy-layers/semantic-fake-backend/" data-link-title="語意級假後端與流程測試" data-link-desc="bug 的成因是對後端行為的假設錯誤、由測試餵資料的 stub 驗證不出來時：建一個持有狀態、模擬已證實後端行為的假後端（test double 分類的 fake），讓流程測試走完整的多服務互動鏈">語意級假後端與流程測試</a></li>
<li>被繞過的鏈路屬於哪一層職責 → <a href="/blog/testing/01-test-strategy-layers/three-layer-definition/" data-link-title="三層定義與職責表" data-link-desc="Unit Test / Protocol Integration Test / Screen State Test 各層職責、驗證目標與盲區的完整論述">三層定義與職責表</a></li>
<li>前一集：bug 為什麼漏掉 → <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 凍結參照失效被 stub 遮蔽</a></li>
</ul>
]]></content:encoded></item></channel></rss>