<?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>模組五：測試設計判斷 on Tarragon</title><link>https://tarrragon.github.io/blog/testing/05-test-design-judgment/</link><description>Recent content in 模組五：測試設計判斷 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 19 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/testing/05-test-design-judgment/index.xml" rel="self" type="application/rss+xml"/><item><title>Mock 邊界判斷決策表</title><link>https://tarrragon.github.io/blog/testing/05-test-design-judgment/mock-boundary-decision/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/05-test-design-judgment/mock-boundary-decision/</guid><description>&lt;p>Mock 的適用範圍由它模擬的層級決定。Mock 忠實模擬 API 層的契約（方法簽名、參數型別），但無法模擬協議層的語意差異和環境層的行為差異。判斷「這個 test 用 mock 夠不夠」的依據是：test 要驗證的行為發生在哪一層。&lt;/p>
&lt;h2 id="決策依據">決策依據&lt;/h2>
&lt;h3 id="mock-夠用的場景">Mock 夠用的場景&lt;/h3>
&lt;p>Test 驗證的行為完全在程式碼內部 — 函式邏輯、狀態機轉換、資料轉換、錯誤處理分支。這些行為不依賴外部服務的協議細節，mock 提供的 API 層模擬已經足夠。&lt;/p>
&lt;p>判斷問題：&lt;strong>如果把 mock 替換成真實服務，test 的斷言結果會不會改變？&lt;/strong> 如果不會改變，mock 夠用。&lt;/p>
&lt;p>例：&lt;code>ConnectionManager&lt;/code> 收到 error 後是否正確切換到 error 狀態 — 不管 error 來自 mock 還是真實 WebSocket，狀態機邏輯相同。Mock 夠用。&lt;/p>
&lt;h3 id="mock-不夠的場景">Mock 不夠的場景&lt;/h3>
&lt;p>Test 要驗證的行為涉及外部服務的協議行為 — frame type 差異、認證流程、編碼格式、逾時行為。Mock 的 API 層模擬跳過了這些行為，test 通過不代表真實互動也通過。&lt;/p>
&lt;p>判斷問題：&lt;strong>Mock 跳過了外部服務的哪些步驟？這些步驟的行為是否影響 test 要驗證的結果？&lt;/strong> 如果是，需要 protocol integration test（&lt;a href="https://tarrragon.github.io/blog/testing/03-protocol-integration-test/" data-link-title="模組三：協議整合測試" data-link-desc="對真實服務驗證 WebSocket / gRPC / HTTP 協議契約 — unit test 和 E2E test 之間的一層">testing 模組三&lt;/a>）。&lt;/p>
&lt;p>例：&lt;code>sendData()&lt;/code> 發送鍵盤輸入 — mock 的 &lt;code>sink.add(dynamic)&lt;/code> 接受任何型別，但真實 &lt;code>IOWebSocketChannel&lt;/code> 對 &lt;code>String&lt;/code> 和 &lt;code>Uint8List&lt;/code> 產生不同 frame type。Mock 不夠。&lt;/p>
&lt;h2 id="決策表">決策表&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>驗證對象&lt;/th>
 &lt;th>Mock 夠用？&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>error 來源不影響處理邏輯&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>mock 可能跳過認證步驟&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料編碼格式&lt;/td>
 &lt;td>不夠&lt;/td>
 &lt;td>mock 不區分編碼差異（text vs binary）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>逾時行為&lt;/td>
 &lt;td>不夠&lt;/td>
 &lt;td>mock 的回應時間和真實服務不同&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>多步驟協議流程&lt;/td>
 &lt;td>不夠&lt;/td>
 &lt;td>mock 可能簡化多步驟為單步&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>並行/競爭條件&lt;/td>
 &lt;td>不夠&lt;/td>
 &lt;td>mock 通常同步回應，無法模擬真實的並行行為&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="灰色地帶的判斷">灰色地帶的判斷&lt;/h2>
&lt;p>有些 test 介於「mock 夠用」和「mock 不夠」之間。例如驗證「連線失敗時顯示 error 訊息」— 觸發失敗的方式可以是 mock 回傳 error（驗證顯示邏輯），也可以是真實服務拒絕連線（驗證真實失敗場景的處理）。&lt;/p>
&lt;p>灰色地帶的判斷策略是：用 mock test 驗證「收到 error 後的處理邏輯」，用 protocol integration test 驗證「真實服務在什麼情況下回傳 error」。兩層 test 各自回答不同問題，不互相替代（&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 各層職責、驗證目標與盲區的完整論述">testing 模組一 三層定義&lt;/a>）。&lt;/p>
&lt;p>Mock 邊界確定後，另一個影響 test 有效性的因素是&lt;a href="https://tarrragon.github.io/blog/testing/05-test-design-judgment/test-data-representativeness/" data-link-title="Test data 代表性" data-link-desc="手寫 vs 錄製 vs 生成三種測試資料來源 — 測試資料的代表性是一個隱性假設，決定了 test 能發現什麼問題">測試資料的代表性&lt;/a> — 測試輸入能否反映真實環境。Mock 遮蔽的結構性原因在 &lt;a href="https://tarrragon.github.io/blog/testing/01-test-strategy-layers/mock-masking-mechanism/" data-link-title="Mock 遮蔽機制分析" data-link-desc="Mock 在 API 層、協議層、環境層之間製造的結構性盲區 — 斷裂點在哪、為什麼 mock 無法也不應該模擬協議行為">testing 模組一 Mock 遮蔽機制分析&lt;/a>中完整展開，判定需要真實服務後的成本評估見 &lt;a href="https://tarrragon.github.io/blog/testing/03-protocol-integration-test/cost-judgment/" data-link-title="成本判斷表" data-link-desc="什麼時候值得寫 protocol integration test、什麼時候用 contract test 或實機測試替代 — 根據服務啟動成本和協議複雜度判斷">testing 模組三 成本判斷表&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Mock 的適用範圍由它模擬的層級決定。Mock 忠實模擬 API 層的契約（方法簽名、參數型別），但無法模擬協議層的語意差異和環境層的行為差異。判斷「這個 test 用 mock 夠不夠」的依據是：test 要驗證的行為發生在哪一層。</p>
<h2 id="決策依據">決策依據</h2>
<h3 id="mock-夠用的場景">Mock 夠用的場景</h3>
<p>Test 驗證的行為完全在程式碼內部 — 函式邏輯、狀態機轉換、資料轉換、錯誤處理分支。這些行為不依賴外部服務的協議細節，mock 提供的 API 層模擬已經足夠。</p>
<p>判斷問題：<strong>如果把 mock 替換成真實服務，test 的斷言結果會不會改變？</strong> 如果不會改變，mock 夠用。</p>
<p>例：<code>ConnectionManager</code> 收到 error 後是否正確切換到 error 狀態 — 不管 error 來自 mock 還是真實 WebSocket，狀態機邏輯相同。Mock 夠用。</p>
<h3 id="mock-不夠的場景">Mock 不夠的場景</h3>
<p>Test 要驗證的行為涉及外部服務的協議行為 — frame type 差異、認證流程、編碼格式、逾時行為。Mock 的 API 層模擬跳過了這些行為，test 通過不代表真實互動也通過。</p>
<p>判斷問題：<strong>Mock 跳過了外部服務的哪些步驟？這些步驟的行為是否影響 test 要驗證的結果？</strong> 如果是，需要 protocol integration test（<a href="/blog/testing/03-protocol-integration-test/" data-link-title="模組三：協議整合測試" data-link-desc="對真實服務驗證 WebSocket / gRPC / HTTP 協議契約 — unit test 和 E2E test 之間的一層">testing 模組三</a>）。</p>
<p>例：<code>sendData()</code> 發送鍵盤輸入 — mock 的 <code>sink.add(dynamic)</code> 接受任何型別，但真實 <code>IOWebSocketChannel</code> 對 <code>String</code> 和 <code>Uint8List</code> 產生不同 frame type。Mock 不夠。</p>
<h2 id="決策表">決策表</h2>
<table>
  <thead>
      <tr>
          <th>驗證對象</th>
          <th>Mock 夠用？</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>error 來源不影響處理邏輯</td>
      </tr>
      <tr>
          <td>資料格式轉換</td>
          <td>夠</td>
          <td>轉換邏輯在程式碼內部</td>
      </tr>
      <tr>
          <td>連線建立成功/失敗</td>
          <td>視情況</td>
          <td>如果只驗證「收到成功/失敗後做什麼」→ 夠</td>
      </tr>
      <tr>
          <td>認證流程完整性</td>
          <td>不夠</td>
          <td>mock 可能跳過認證步驟</td>
      </tr>
      <tr>
          <td>資料編碼格式</td>
          <td>不夠</td>
          <td>mock 不區分編碼差異（text vs binary）</td>
      </tr>
      <tr>
          <td>逾時行為</td>
          <td>不夠</td>
          <td>mock 的回應時間和真實服務不同</td>
      </tr>
      <tr>
          <td>多步驟協議流程</td>
          <td>不夠</td>
          <td>mock 可能簡化多步驟為單步</td>
      </tr>
      <tr>
          <td>並行/競爭條件</td>
          <td>不夠</td>
          <td>mock 通常同步回應，無法模擬真實的並行行為</td>
      </tr>
  </tbody>
</table>
<h2 id="灰色地帶的判斷">灰色地帶的判斷</h2>
<p>有些 test 介於「mock 夠用」和「mock 不夠」之間。例如驗證「連線失敗時顯示 error 訊息」— 觸發失敗的方式可以是 mock 回傳 error（驗證顯示邏輯），也可以是真實服務拒絕連線（驗證真實失敗場景的處理）。</p>
<p>灰色地帶的判斷策略是：用 mock test 驗證「收到 error 後的處理邏輯」，用 protocol integration test 驗證「真實服務在什麼情況下回傳 error」。兩層 test 各自回答不同問題，不互相替代（<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 各層職責、驗證目標與盲區的完整論述">testing 模組一 三層定義</a>）。</p>
<p>Mock 邊界確定後，另一個影響 test 有效性的因素是<a href="/blog/testing/05-test-design-judgment/test-data-representativeness/" data-link-title="Test data 代表性" data-link-desc="手寫 vs 錄製 vs 生成三種測試資料來源 — 測試資料的代表性是一個隱性假設，決定了 test 能發現什麼問題">測試資料的代表性</a> — 測試輸入能否反映真實環境。Mock 遮蔽的結構性原因在 <a href="/blog/testing/01-test-strategy-layers/mock-masking-mechanism/" data-link-title="Mock 遮蔽機制分析" data-link-desc="Mock 在 API 層、協議層、環境層之間製造的結構性盲區 — 斷裂點在哪、為什麼 mock 無法也不應該模擬協議行為">testing 模組一 Mock 遮蔽機制分析</a>中完整展開，判定需要真實服務後的成本評估見 <a href="/blog/testing/03-protocol-integration-test/cost-judgment/" data-link-title="成本判斷表" data-link-desc="什麼時候值得寫 protocol integration test、什麼時候用 contract test 或實機測試替代 — 根據服務啟動成本和協議複雜度判斷">testing 模組三 成本判斷表</a>。</p>
]]></content:encoded></item><item><title>Test data 代表性</title><link>https://tarrragon.github.io/blog/testing/05-test-design-judgment/test-data-representativeness/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/05-test-design-judgment/test-data-representativeness/</guid><description>&lt;p>測試資料的代表性是指測試輸入能多大程度反映真實環境的輸入分佈。「測試資料能代表真實環境」是每個 test 的隱性假設 — 這個假設成立時 test 有效，不成立時 test 通過但問題仍在。&lt;/p>
&lt;h2 id="代表性問題的案例">代表性問題的案例&lt;/h2>
&lt;p>app_tunnel 的 ANSI parser 有 18 個 test，全部通過。測試資料是手寫的 SGR 色彩碼（&lt;code>\x1B[31mhello\x1B[0m&lt;/code>），parser 正確解析這類序列。&lt;/p>
&lt;p>真實 zsh 啟動後送出的控制序列包含 OSC 標題設定、CSI private mode、字元集指定等至少 5 種類型。Parser 只認識 SGR，其他全部透傳為亂碼（&lt;a href="https://tarrragon.github.io/blog/testing/cases/ansi-parser-test-data-blindspot/" data-link-title="T.C3 ANSI parser 測試資料不覆蓋真實 shell output" data-link-desc="ANSI parser 只處理基本 SGR 色彩碼、unit test 用手寫乾淨字串驗證 — 真實 zsh prompt 送出 OSC 標題設定、CSI private mode 游標隱藏、括號貼上模式等數十種控制序列，全部殘留為亂碼">T.C3&lt;/a>）。&lt;/p>
&lt;p>18 個 test 覆蓋了 1 種序列類型。測試資料的代表性假設（「SGR 就是主要的序列類型」）和真實環境不符。&lt;/p>
&lt;h2 id="三種測試資料來源">三種測試資料來源&lt;/h2>
&lt;h3 id="手寫">手寫&lt;/h3>
&lt;p>開發者根據對輸入格式的理解手動建構測試字串。&lt;/p>
&lt;p>優點：精確控制、容易理解、可以針對特定邊界條件設計。&lt;/p>
&lt;p>缺點：受限於開發者對輸入分佈的認知。如果開發者不知道真實環境有哪些輸入類型，手寫的測試資料就是開發者認知的子集 — T.C3 就是這個模式。&lt;/p>
&lt;p>適合場景：格式規格明確且有限（JSON schema、固定格式的設定檔）、邊界條件測試（空值、最大長度、特殊字元）。&lt;/p>
&lt;h3 id="錄製">錄製&lt;/h3>
&lt;p>從真實環境擷取實際的輸入資料，作為 test 的輸入。&lt;/p>
&lt;p>優點：直接反映真實環境的輸入分佈，包含開發者不知道的輸入類型。&lt;/p>
&lt;p>缺點：錄製的資料可能包含敏感資訊（需要脫敏）、資料量可能大（需要挑選代表性樣本）、真實環境的輸入可能隨時間改變（錄製的資料可能過時）。&lt;/p>
&lt;p>適合場景：輸入格式複雜且規格不完整（終端機 escape 序列、網路封包、使用者產生的內容）、parser 類的功能（需要知道「真實輸入長什麼樣」）。&lt;/p>
&lt;p>T.C3 如果用錄製的真實 zsh 啟動輸出作為測試資料，OSC 和 CSI private mode 會自然出現在輸入中。即使 parser 仍然不處理這些序列，test 至少能讓開發者看到「有 5 種序列類型，我只處理了 1 種」。&lt;/p>
&lt;h3 id="生成property-based-testing">生成（Property-based testing）&lt;/h3>
&lt;p>用 generator 自動產生大量隨機或半隨機的輸入，驗證 parser 的行為是否符合通用性質（不崩潰、輸出長度 &amp;lt;= 輸入長度、冪等性）。&lt;/p>
&lt;p>優點：覆蓋人類想不到的 edge case、發現意外的崩潰或無限迴圈。&lt;/p>
&lt;p>缺點：不針對特定功能驗證（驗證的是通用性質，不是「OSC 序列是否被正確處理」）、generator 本身需要維護。&lt;/p>
&lt;p>適合場景：parser、serializer、codec 等輸入格式複雜的功能。和手寫 test 互補 — 手寫驗證特定行為正確性，生成驗證通用穩定性。&lt;/p>
&lt;h2 id="兩類-test-的分工">兩類 test 的分工&lt;/h2>
&lt;p>T.C3 的策略建議是把 test 分成兩類：&lt;/p>
&lt;p>&lt;strong>功能正確性 test&lt;/strong>：用手寫乾淨字串驗證 parser 對已知序列的處理正確性。&lt;code>\x1B[31mhello\x1B[0m&lt;/code> 應該產生紅色 token — 這是功能規格的驗證。&lt;/p>
&lt;p>&lt;strong>環境相容性 test&lt;/strong>：用錄製的真實輸出驗證 parser 在真實環境中的表現。不斷言「每個序列都被正確處理」，而是斷言「沒有崩潰」「沒有未處理序列殘留在可見輸出中」。&lt;/p>
&lt;p>兩類 test 回答不同問題。功能正確性回答「parser 的邏輯對不對」，環境相容性回答「parser 在真實環境中夠不夠用」。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>Assertion 的品質判斷 → &lt;a href="https://tarrragon.github.io/blog/testing/05-test-design-judgment/assertion-quality/" data-link-title="Assertion 品質三問" data-link-desc="斷言的是行為嗎？能區分正確和錯誤嗎？會 flaky 嗎？— 三個問題判斷 assertion 是否有效">Assertion 品質三問&lt;/a>&lt;/li>
&lt;li>Mock 邊界的判斷 → &lt;a href="https://tarrragon.github.io/blog/testing/05-test-design-judgment/mock-boundary-decision/" data-link-title="Mock 邊界判斷決策表" data-link-desc="什麼時候 mock 夠用、什麼時候需要真實服務 — 從 API 層 / 協議層 / 環境層的斷裂點判斷 mock 的適用範圍">Mock 邊界判斷決策表&lt;/a>&lt;/li>
&lt;li>Protocol integration test 用真實服務輸出 → &lt;a href="https://tarrragon.github.io/blog/testing/03-protocol-integration-test/websocket-protocol-test/" data-link-title="WebSocket 協議測試實作" data-link-desc="對真實 ttyd 驗證 frame type 和 auth handshake — 從 T.C1 和 T.C2 的教訓推導出的 protocol integration test 設計">testing 模組三 WebSocket 協議測試&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>測試資料的代表性是指測試輸入能多大程度反映真實環境的輸入分佈。「測試資料能代表真實環境」是每個 test 的隱性假設 — 這個假設成立時 test 有效，不成立時 test 通過但問題仍在。</p>
<h2 id="代表性問題的案例">代表性問題的案例</h2>
<p>app_tunnel 的 ANSI parser 有 18 個 test，全部通過。測試資料是手寫的 SGR 色彩碼（<code>\x1B[31mhello\x1B[0m</code>），parser 正確解析這類序列。</p>
<p>真實 zsh 啟動後送出的控制序列包含 OSC 標題設定、CSI private mode、字元集指定等至少 5 種類型。Parser 只認識 SGR，其他全部透傳為亂碼（<a href="/blog/testing/cases/ansi-parser-test-data-blindspot/" data-link-title="T.C3 ANSI parser 測試資料不覆蓋真實 shell output" data-link-desc="ANSI parser 只處理基本 SGR 色彩碼、unit test 用手寫乾淨字串驗證 — 真實 zsh prompt 送出 OSC 標題設定、CSI private mode 游標隱藏、括號貼上模式等數十種控制序列，全部殘留為亂碼">T.C3</a>）。</p>
<p>18 個 test 覆蓋了 1 種序列類型。測試資料的代表性假設（「SGR 就是主要的序列類型」）和真實環境不符。</p>
<h2 id="三種測試資料來源">三種測試資料來源</h2>
<h3 id="手寫">手寫</h3>
<p>開發者根據對輸入格式的理解手動建構測試字串。</p>
<p>優點：精確控制、容易理解、可以針對特定邊界條件設計。</p>
<p>缺點：受限於開發者對輸入分佈的認知。如果開發者不知道真實環境有哪些輸入類型，手寫的測試資料就是開發者認知的子集 — T.C3 就是這個模式。</p>
<p>適合場景：格式規格明確且有限（JSON schema、固定格式的設定檔）、邊界條件測試（空值、最大長度、特殊字元）。</p>
<h3 id="錄製">錄製</h3>
<p>從真實環境擷取實際的輸入資料，作為 test 的輸入。</p>
<p>優點：直接反映真實環境的輸入分佈，包含開發者不知道的輸入類型。</p>
<p>缺點：錄製的資料可能包含敏感資訊（需要脫敏）、資料量可能大（需要挑選代表性樣本）、真實環境的輸入可能隨時間改變（錄製的資料可能過時）。</p>
<p>適合場景：輸入格式複雜且規格不完整（終端機 escape 序列、網路封包、使用者產生的內容）、parser 類的功能（需要知道「真實輸入長什麼樣」）。</p>
<p>T.C3 如果用錄製的真實 zsh 啟動輸出作為測試資料，OSC 和 CSI private mode 會自然出現在輸入中。即使 parser 仍然不處理這些序列，test 至少能讓開發者看到「有 5 種序列類型，我只處理了 1 種」。</p>
<h3 id="生成property-based-testing">生成（Property-based testing）</h3>
<p>用 generator 自動產生大量隨機或半隨機的輸入，驗證 parser 的行為是否符合通用性質（不崩潰、輸出長度 &lt;= 輸入長度、冪等性）。</p>
<p>優點：覆蓋人類想不到的 edge case、發現意外的崩潰或無限迴圈。</p>
<p>缺點：不針對特定功能驗證（驗證的是通用性質，不是「OSC 序列是否被正確處理」）、generator 本身需要維護。</p>
<p>適合場景：parser、serializer、codec 等輸入格式複雜的功能。和手寫 test 互補 — 手寫驗證特定行為正確性，生成驗證通用穩定性。</p>
<h2 id="兩類-test-的分工">兩類 test 的分工</h2>
<p>T.C3 的策略建議是把 test 分成兩類：</p>
<p><strong>功能正確性 test</strong>：用手寫乾淨字串驗證 parser 對已知序列的處理正確性。<code>\x1B[31mhello\x1B[0m</code> 應該產生紅色 token — 這是功能規格的驗證。</p>
<p><strong>環境相容性 test</strong>：用錄製的真實輸出驗證 parser 在真實環境中的表現。不斷言「每個序列都被正確處理」，而是斷言「沒有崩潰」「沒有未處理序列殘留在可見輸出中」。</p>
<p>兩類 test 回答不同問題。功能正確性回答「parser 的邏輯對不對」，環境相容性回答「parser 在真實環境中夠不夠用」。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Assertion 的品質判斷 → <a href="/blog/testing/05-test-design-judgment/assertion-quality/" data-link-title="Assertion 品質三問" data-link-desc="斷言的是行為嗎？能區分正確和錯誤嗎？會 flaky 嗎？— 三個問題判斷 assertion 是否有效">Assertion 品質三問</a></li>
<li>Mock 邊界的判斷 → <a href="/blog/testing/05-test-design-judgment/mock-boundary-decision/" data-link-title="Mock 邊界判斷決策表" data-link-desc="什麼時候 mock 夠用、什麼時候需要真實服務 — 從 API 層 / 協議層 / 環境層的斷裂點判斷 mock 的適用範圍">Mock 邊界判斷決策表</a></li>
<li>Protocol integration test 用真實服務輸出 → <a href="/blog/testing/03-protocol-integration-test/websocket-protocol-test/" data-link-title="WebSocket 協議測試實作" data-link-desc="對真實 ttyd 驗證 frame type 和 auth handshake — 從 T.C1 和 T.C2 的教訓推導出的 protocol integration test 設計">testing 模組三 WebSocket 協議測試</a></li>
</ul>
]]></content:encoded></item><item><title>Assertion 品質三問</title><link>https://tarrragon.github.io/blog/testing/05-test-design-judgment/assertion-quality/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/05-test-design-judgment/assertion-quality/</guid><description>&lt;p>Assertion 是 test 的結論 — 「我認為程式碼的行為應該是 X」。Assertion 的品質決定了 test 的有效性：無效的 assertion 讓 test 通過但問題仍在，或讓 test 隨機失敗但問題不在程式碼。&lt;/p>
&lt;h2 id="三個判斷問題">三個判斷問題&lt;/h2>
&lt;h3 id="斷言的是行為嗎">斷言的是行為嗎&lt;/h3>
&lt;p>Assertion 應該斷言程式碼的外部可觀察行為（回傳值、狀態變化、副作用），而非內部實作細節（私有變數的值、呼叫次數、執行順序）。&lt;/p>
&lt;p>斷言行為的 test 在重構時不需要改 — 只要行為不變，test 就通過。斷言實作的 test 在任何內部調整時都會壞掉，即使行為完全正確。&lt;/p>
&lt;p>例：驗證「parser 正確解析紅色文字」時，斷言 token 的顏色屬性（行為）比斷言 parser 內部的 state machine 走了哪些步驟（實作）更穩定。&lt;/p>
&lt;h3 id="能區分正確和錯誤嗎">能區分正確和錯誤嗎&lt;/h3>
&lt;p>Assertion 應該在程式碼正確時通過、錯誤時失敗。如果 assertion 無論程式碼正確或錯誤都通過，這個 assertion 沒有提供保護。&lt;/p>
&lt;p>常見的無效 assertion：&lt;/p>
&lt;p>&lt;strong>斷言不為 null&lt;/strong>：&lt;code>expect(result, isNotNull)&lt;/code> 只驗證「有回傳值」，不驗證「回傳值正確」。回傳錯誤的值也會通過。&lt;/p>
&lt;p>&lt;strong>斷言型別&lt;/strong>：&lt;code>expect(result, isA&amp;lt;List&amp;gt;())&lt;/code> 只驗證「回傳 List」，不驗證 List 的內容。空 List 和錯誤內容的 List 都會通過。&lt;/p>
&lt;p>&lt;strong>斷言包含&lt;/strong>：&lt;code>expect(result, contains('error'))&lt;/code> 驗證字串包含 &amp;rsquo;error&amp;rsquo;，但如果回傳 &amp;rsquo;no error occurred&amp;rsquo;（正確情境）也包含 &amp;rsquo;error&amp;rsquo; — assertion 無法區分正確和錯誤。&lt;/p>
&lt;p>T.C3 的 parser test 斷言 &lt;code>expect(tokens.first, isA&amp;lt;TextToken&amp;gt;())&lt;/code> — 驗證 token 型別是 TextToken。但正確解析和透傳亂碼都可能產生 TextToken，assertion 無法區分（本章合成，TF-5 Derive — 透傳的靜默副作用和 assertion 的區分力有 tension）。&lt;/p>
&lt;h3 id="會-flaky-嗎">會 flaky 嗎&lt;/h3>
&lt;p>Assertion 是否依賴非確定性因素 — 時間、隨機數、外部服務狀態、執行順序。如果是，test 可能在程式碼正確時失敗（false negative），降低團隊對 test 的信任。&lt;/p>
&lt;p>常見的 flaky assertion 來源：&lt;/p>
&lt;ul>
&lt;li>依賴 &lt;code>DateTime.now()&lt;/code> 或 &lt;code>stopwatch.elapsed&lt;/code> — 時間精度和系統負載影響結果&lt;/li>
&lt;li>依賴特定的執行順序 — &lt;code>Set&lt;/code> 或 &lt;code>Map&lt;/code> 的迭代順序不保證&lt;/li>
&lt;li>依賴外部服務的回應時間 — 網路延遲導致 timeout&lt;/li>
&lt;/ul>
&lt;h2 id="assertion-改善的操作步驟">Assertion 改善的操作步驟&lt;/h2>
&lt;p>對既有的 test assertion 逐一問三個問題，標記需要改善的：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>行為 check&lt;/strong>：assertion 斷言的是 public API 的回傳值或狀態嗎？如果斷言私有變數或呼叫次數，考慮改成行為斷言。&lt;/li>
&lt;li>&lt;strong>區分 check&lt;/strong>：把 assertion 改成反向（&lt;code>expect(result, 'wrong_value')&lt;/code>），test 會失敗嗎？如果 assertion 太寬鬆（isNotNull、isA），test 可能在錯誤的情況下也通過。&lt;/li>
&lt;li>&lt;strong>穩定 check&lt;/strong>：連續跑 10 次，每次都通過嗎？如果有 flaky，找到依賴的非確定性因素。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>Flaky test 的系統性根因分類 → &lt;a href="https://tarrragon.github.io/blog/testing/05-test-design-judgment/flaky-test-root-cause/" data-link-title="Flaky test 根因分類" data-link-desc="計時依賴 / 環境差異 / 資源競爭 / 非確定性輸出 — 四類 flaky test 根因的辨識和處理策略">Flaky test 根因分類&lt;/a>&lt;/li>
&lt;li>測試資料的代表性 → &lt;a href="https://tarrragon.github.io/blog/testing/05-test-design-judgment/test-data-representativeness/" data-link-title="Test data 代表性" data-link-desc="手寫 vs 錄製 vs 生成三種測試資料來源 — 測試資料的代表性是一個隱性假設，決定了 test 能發現什麼問題">Test data 代表性&lt;/a>&lt;/li>
&lt;li>Mock 邊界判斷 → &lt;a href="https://tarrragon.github.io/blog/testing/05-test-design-judgment/mock-boundary-decision/" data-link-title="Mock 邊界判斷決策表" data-link-desc="什麼時候 mock 夠用、什麼時候需要真實服務 — 從 API 層 / 協議層 / 環境層的斷裂點判斷 mock 的適用範圍">Mock 邊界判斷決策表&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Assertion 是 test 的結論 — 「我認為程式碼的行為應該是 X」。Assertion 的品質決定了 test 的有效性：無效的 assertion 讓 test 通過但問題仍在，或讓 test 隨機失敗但問題不在程式碼。</p>
<h2 id="三個判斷問題">三個判斷問題</h2>
<h3 id="斷言的是行為嗎">斷言的是行為嗎</h3>
<p>Assertion 應該斷言程式碼的外部可觀察行為（回傳值、狀態變化、副作用），而非內部實作細節（私有變數的值、呼叫次數、執行順序）。</p>
<p>斷言行為的 test 在重構時不需要改 — 只要行為不變，test 就通過。斷言實作的 test 在任何內部調整時都會壞掉，即使行為完全正確。</p>
<p>例：驗證「parser 正確解析紅色文字」時，斷言 token 的顏色屬性（行為）比斷言 parser 內部的 state machine 走了哪些步驟（實作）更穩定。</p>
<h3 id="能區分正確和錯誤嗎">能區分正確和錯誤嗎</h3>
<p>Assertion 應該在程式碼正確時通過、錯誤時失敗。如果 assertion 無論程式碼正確或錯誤都通過，這個 assertion 沒有提供保護。</p>
<p>常見的無效 assertion：</p>
<p><strong>斷言不為 null</strong>：<code>expect(result, isNotNull)</code> 只驗證「有回傳值」，不驗證「回傳值正確」。回傳錯誤的值也會通過。</p>
<p><strong>斷言型別</strong>：<code>expect(result, isA&lt;List&gt;())</code> 只驗證「回傳 List」，不驗證 List 的內容。空 List 和錯誤內容的 List 都會通過。</p>
<p><strong>斷言包含</strong>：<code>expect(result, contains('error'))</code> 驗證字串包含 &rsquo;error&rsquo;，但如果回傳 &rsquo;no error occurred&rsquo;（正確情境）也包含 &rsquo;error&rsquo; — assertion 無法區分正確和錯誤。</p>
<p>T.C3 的 parser test 斷言 <code>expect(tokens.first, isA&lt;TextToken&gt;())</code> — 驗證 token 型別是 TextToken。但正確解析和透傳亂碼都可能產生 TextToken，assertion 無法區分（本章合成，TF-5 Derive — 透傳的靜默副作用和 assertion 的區分力有 tension）。</p>
<h3 id="會-flaky-嗎">會 flaky 嗎</h3>
<p>Assertion 是否依賴非確定性因素 — 時間、隨機數、外部服務狀態、執行順序。如果是，test 可能在程式碼正確時失敗（false negative），降低團隊對 test 的信任。</p>
<p>常見的 flaky assertion 來源：</p>
<ul>
<li>依賴 <code>DateTime.now()</code> 或 <code>stopwatch.elapsed</code> — 時間精度和系統負載影響結果</li>
<li>依賴特定的執行順序 — <code>Set</code> 或 <code>Map</code> 的迭代順序不保證</li>
<li>依賴外部服務的回應時間 — 網路延遲導致 timeout</li>
</ul>
<h2 id="assertion-改善的操作步驟">Assertion 改善的操作步驟</h2>
<p>對既有的 test assertion 逐一問三個問題，標記需要改善的：</p>
<ol>
<li><strong>行為 check</strong>：assertion 斷言的是 public API 的回傳值或狀態嗎？如果斷言私有變數或呼叫次數，考慮改成行為斷言。</li>
<li><strong>區分 check</strong>：把 assertion 改成反向（<code>expect(result, 'wrong_value')</code>），test 會失敗嗎？如果 assertion 太寬鬆（isNotNull、isA），test 可能在錯誤的情況下也通過。</li>
<li><strong>穩定 check</strong>：連續跑 10 次，每次都通過嗎？如果有 flaky，找到依賴的非確定性因素。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Flaky test 的系統性根因分類 → <a href="/blog/testing/05-test-design-judgment/flaky-test-root-cause/" data-link-title="Flaky test 根因分類" data-link-desc="計時依賴 / 環境差異 / 資源競爭 / 非確定性輸出 — 四類 flaky test 根因的辨識和處理策略">Flaky test 根因分類</a></li>
<li>測試資料的代表性 → <a href="/blog/testing/05-test-design-judgment/test-data-representativeness/" data-link-title="Test data 代表性" data-link-desc="手寫 vs 錄製 vs 生成三種測試資料來源 — 測試資料的代表性是一個隱性假設，決定了 test 能發現什麼問題">Test data 代表性</a></li>
<li>Mock 邊界判斷 → <a href="/blog/testing/05-test-design-judgment/mock-boundary-decision/" data-link-title="Mock 邊界判斷決策表" data-link-desc="什麼時候 mock 夠用、什麼時候需要真實服務 — 從 API 層 / 協議層 / 環境層的斷裂點判斷 mock 的適用範圍">Mock 邊界判斷決策表</a></li>
</ul>
]]></content:encoded></item><item><title>Flaky test 根因分類</title><link>https://tarrragon.github.io/blog/testing/05-test-design-judgment/flaky-test-root-cause/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/05-test-design-judgment/flaky-test-root-cause/</guid><description>&lt;p>Flaky test 是指在程式碼沒有改變的情況下，test 的結果在通過和失敗之間隨機切換。Flaky test 侵蝕團隊對 test suite 的信任 — 如果 test 經常「隨便」失敗，開發者會習慣性地 re-run 而非調查失敗原因，真正的 bug 可能在 re-run 中被忽略。&lt;/p>
&lt;h2 id="四類根因">四類根因&lt;/h2>
&lt;h3 id="計時依賴">計時依賴&lt;/h3>
&lt;p>Test 依賴特定的時間條件 — timeout、delay、animation duration。系統負載不同時，時間條件可能滿足也可能不滿足。&lt;/p>
&lt;p>常見模式：&lt;/p>
&lt;ul>
&lt;li>&lt;code>await Future.delayed(Duration(seconds: 2))&lt;/code> + assertion — 如果操作在 2 秒內完成，test 通過；如果 CI 機器負載高導致操作超過 2 秒，test 失敗&lt;/li>
&lt;li>&lt;code>expect(stopwatch.elapsed, lessThan(Duration(seconds: 1)))&lt;/code> — 效能斷言在不同機器上結果不同&lt;/li>
&lt;/ul>
&lt;p>處理策略：用事件驅動代替 timeout。等待 &lt;code>stream.first&lt;/code> 代替 &lt;code>delay(2s) + check&lt;/code>；用 completion signal 代替固定等待時間。如果必須用 timeout，設定寬裕的上限（10x 預期時間）而非精確的預期值。&lt;/p>
&lt;h3 id="環境差異">環境差異&lt;/h3>
&lt;p>Test 在不同環境下行為不同 — 作業系統、檔案系統、時區、locale、DNS 解析。&lt;/p>
&lt;p>常見模式：&lt;/p>
&lt;ul>
&lt;li>檔案路徑分隔符（&lt;code>/&lt;/code> vs &lt;code>\&lt;/code>）在不同 OS 下不同&lt;/li>
&lt;li>時間格式化結果依時區而定（UTC vs local）&lt;/li>
&lt;li>浮點數比較因 CPU 架構不同有微小差異&lt;/li>
&lt;/ul>
&lt;p>處理策略：用 &lt;code>path.join&lt;/code> 代替硬編碼路徑；時間操作用 UTC；浮點比較用 &lt;code>closeTo&lt;/code> 代替精確比較。在 CI 中固定環境變數（&lt;code>TZ=UTC&lt;/code>、&lt;code>LANG=en_US.UTF-8&lt;/code>）。&lt;/p>
&lt;h3 id="資源競爭">資源競爭&lt;/h3>
&lt;p>Test 依賴共享資源（port、暫存檔、資料庫行）— 平行執行時多個 test 同時存取同一資源，結果依賴執行順序。&lt;/p>
&lt;p>常見模式：&lt;/p>
&lt;ul>
&lt;li>多個 test 監聽同一個 port — 第二個綁定失敗&lt;/li>
&lt;li>多個 test 寫入同一個暫存檔 — 內容被覆蓋&lt;/li>
&lt;li>多個 test 操作同一個資料庫 table — 資料互相干擾&lt;/li>
&lt;/ul>
&lt;p>處理策略：每個 test 使用獨立的資源（隨機 port、唯一檔名、隔離的資料庫 schema）。如果資源無法隔離，sequential 執行相關 test（&lt;code>@sequential&lt;/code> 標註）。&lt;/p>
&lt;h3 id="非確定性輸出">非確定性輸出&lt;/h3>
&lt;p>程式碼的輸出本身不確定 — &lt;code>Set&lt;/code> 的迭代順序、&lt;code>Map&lt;/code> 的 key 順序、非同步操作的完成順序。&lt;/p>
&lt;p>常見模式：&lt;/p>
&lt;ul>
&lt;li>斷言 &lt;code>Set&lt;/code> 的 &lt;code>toString()&lt;/code> 結果等於特定字串 — &lt;code>Set&lt;/code> 的迭代順序不保證&lt;/li>
&lt;li>斷言 &lt;code>Future.wait([a, b]).then((results) =&amp;gt; results[0])&lt;/code> — &lt;code>a&lt;/code> 和 &lt;code>b&lt;/code> 的完成順序不固定&lt;/li>
&lt;li>斷言 JSON 序列化的 key 順序 — &lt;code>Map&lt;/code> 的 key 順序在不同實作中不同&lt;/li>
&lt;/ul>
&lt;p>處理策略：不斷言順序（用 &lt;code>containsAll&lt;/code> 代替 &lt;code>equals&lt;/code> 比較集合）；不斷言序列化格式（反序列化後比較值）；用 &lt;code>completion&lt;/code> matcher 代替順序假設。&lt;/p>
&lt;h2 id="診斷步驟">診斷步驟&lt;/h2>
&lt;p>發現疑似 flaky test 時的診斷步驟：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>確認 flaky&lt;/strong>：在乾淨環境連續跑 20 次，確認失敗是隨機的（如果每次都失敗，是 bug 不是 flaky）&lt;/li>
&lt;li>&lt;strong>收集失敗訊息&lt;/strong>：記錄每次失敗的 assertion 訊息、stack trace、環境資訊（OS 版本、CI 機器 ID）&lt;/li>
&lt;li>&lt;strong>分類&lt;/strong>：失敗訊息指向時間（timeout）→ 計時依賴；指向值不同 → 非確定性或環境差異；指向連接失敗 → 資源競爭&lt;/li>
&lt;li>&lt;strong>修復&lt;/strong>：根據分類使用對應的處理策略&lt;/li>
&lt;/ol>
&lt;p>分類和修復之外，flaky test 的根因有時來自 assertion 本身的設計 — &lt;a href="https://tarrragon.github.io/blog/testing/05-test-design-judgment/assertion-quality/" data-link-title="Assertion 品質三問" data-link-desc="斷言的是行為嗎？能區分正確和錯誤嗎？會 flaky 嗎？— 三個問題判斷 assertion 是否有效">Assertion 品質三問&lt;/a>提供判斷 assertion 是否有效的框架。如果 flaky 的根因是 mock 和真實服務的行為差異，回到 &lt;a href="https://tarrragon.github.io/blog/testing/05-test-design-judgment/mock-boundary-decision/" data-link-title="Mock 邊界判斷決策表" data-link-desc="什麼時候 mock 夠用、什麼時候需要真實服務 — 從 API 層 / 協議層 / 環境層的斷裂點判斷 mock 的適用範圍">Mock 邊界判斷決策表&lt;/a>判斷 mock 是否還適用。Protocol integration test 在 CI 中的&lt;a href="https://tarrragon.github.io/blog/testing/03-protocol-integration-test/service-fixture-management/" data-link-title="CI 中的服務 fixture 管理" data-link-desc="在 CI 中啟動和停止真實服務的 test harness 設計 — Process.start / Docker / testcontainers 三種方案的適用場景">服務 fixture 管理&lt;/a>也是 flaky 的常見來源 — 服務啟動不完全就開始跑 test。&lt;/p></description><content:encoded><![CDATA[<p>Flaky test 是指在程式碼沒有改變的情況下，test 的結果在通過和失敗之間隨機切換。Flaky test 侵蝕團隊對 test suite 的信任 — 如果 test 經常「隨便」失敗，開發者會習慣性地 re-run 而非調查失敗原因，真正的 bug 可能在 re-run 中被忽略。</p>
<h2 id="四類根因">四類根因</h2>
<h3 id="計時依賴">計時依賴</h3>
<p>Test 依賴特定的時間條件 — timeout、delay、animation duration。系統負載不同時，時間條件可能滿足也可能不滿足。</p>
<p>常見模式：</p>
<ul>
<li><code>await Future.delayed(Duration(seconds: 2))</code> + assertion — 如果操作在 2 秒內完成，test 通過；如果 CI 機器負載高導致操作超過 2 秒，test 失敗</li>
<li><code>expect(stopwatch.elapsed, lessThan(Duration(seconds: 1)))</code> — 效能斷言在不同機器上結果不同</li>
</ul>
<p>處理策略：用事件驅動代替 timeout。等待 <code>stream.first</code> 代替 <code>delay(2s) + check</code>；用 completion signal 代替固定等待時間。如果必須用 timeout，設定寬裕的上限（10x 預期時間）而非精確的預期值。</p>
<h3 id="環境差異">環境差異</h3>
<p>Test 在不同環境下行為不同 — 作業系統、檔案系統、時區、locale、DNS 解析。</p>
<p>常見模式：</p>
<ul>
<li>檔案路徑分隔符（<code>/</code> vs <code>\</code>）在不同 OS 下不同</li>
<li>時間格式化結果依時區而定（UTC vs local）</li>
<li>浮點數比較因 CPU 架構不同有微小差異</li>
</ul>
<p>處理策略：用 <code>path.join</code> 代替硬編碼路徑；時間操作用 UTC；浮點比較用 <code>closeTo</code> 代替精確比較。在 CI 中固定環境變數（<code>TZ=UTC</code>、<code>LANG=en_US.UTF-8</code>）。</p>
<h3 id="資源競爭">資源競爭</h3>
<p>Test 依賴共享資源（port、暫存檔、資料庫行）— 平行執行時多個 test 同時存取同一資源，結果依賴執行順序。</p>
<p>常見模式：</p>
<ul>
<li>多個 test 監聽同一個 port — 第二個綁定失敗</li>
<li>多個 test 寫入同一個暫存檔 — 內容被覆蓋</li>
<li>多個 test 操作同一個資料庫 table — 資料互相干擾</li>
</ul>
<p>處理策略：每個 test 使用獨立的資源（隨機 port、唯一檔名、隔離的資料庫 schema）。如果資源無法隔離，sequential 執行相關 test（<code>@sequential</code> 標註）。</p>
<h3 id="非確定性輸出">非確定性輸出</h3>
<p>程式碼的輸出本身不確定 — <code>Set</code> 的迭代順序、<code>Map</code> 的 key 順序、非同步操作的完成順序。</p>
<p>常見模式：</p>
<ul>
<li>斷言 <code>Set</code> 的 <code>toString()</code> 結果等於特定字串 — <code>Set</code> 的迭代順序不保證</li>
<li>斷言 <code>Future.wait([a, b]).then((results) =&gt; results[0])</code> — <code>a</code> 和 <code>b</code> 的完成順序不固定</li>
<li>斷言 JSON 序列化的 key 順序 — <code>Map</code> 的 key 順序在不同實作中不同</li>
</ul>
<p>處理策略：不斷言順序（用 <code>containsAll</code> 代替 <code>equals</code> 比較集合）；不斷言序列化格式（反序列化後比較值）；用 <code>completion</code> matcher 代替順序假設。</p>
<h2 id="診斷步驟">診斷步驟</h2>
<p>發現疑似 flaky test 時的診斷步驟：</p>
<ol>
<li><strong>確認 flaky</strong>：在乾淨環境連續跑 20 次，確認失敗是隨機的（如果每次都失敗，是 bug 不是 flaky）</li>
<li><strong>收集失敗訊息</strong>：記錄每次失敗的 assertion 訊息、stack trace、環境資訊（OS 版本、CI 機器 ID）</li>
<li><strong>分類</strong>：失敗訊息指向時間（timeout）→ 計時依賴；指向值不同 → 非確定性或環境差異；指向連接失敗 → 資源競爭</li>
<li><strong>修復</strong>：根據分類使用對應的處理策略</li>
</ol>
<p>分類和修復之外，flaky test 的根因有時來自 assertion 本身的設計 — <a href="/blog/testing/05-test-design-judgment/assertion-quality/" data-link-title="Assertion 品質三問" data-link-desc="斷言的是行為嗎？能區分正確和錯誤嗎？會 flaky 嗎？— 三個問題判斷 assertion 是否有效">Assertion 品質三問</a>提供判斷 assertion 是否有效的框架。如果 flaky 的根因是 mock 和真實服務的行為差異，回到 <a href="/blog/testing/05-test-design-judgment/mock-boundary-decision/" data-link-title="Mock 邊界判斷決策表" data-link-desc="什麼時候 mock 夠用、什麼時候需要真實服務 — 從 API 層 / 協議層 / 環境層的斷裂點判斷 mock 的適用範圍">Mock 邊界判斷決策表</a>判斷 mock 是否還適用。Protocol integration test 在 CI 中的<a href="/blog/testing/03-protocol-integration-test/service-fixture-management/" data-link-title="CI 中的服務 fixture 管理" data-link-desc="在 CI 中啟動和停止真實服務的 test harness 設計 — Process.start / Docker / testcontainers 三種方案的適用場景">服務 fixture 管理</a>也是 flaky 的常見來源 — 服務啟動不完全就開始跑 test。</p>
]]></content:encoded></item></channel></rss>