<?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-Design on Tarragon</title><link>https://tarrragon.github.io/blog/tags/test-design/</link><description>Recent content in Test-Design 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/tags/test-design/index.xml" rel="self" type="application/rss+xml"/><item><title>「名義 integration test」的識別與修正</title><link>https://tarrragon.github.io/blog/testing/01-test-strategy-layers/nominal-integration-test/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/01-test-strategy-layers/nominal-integration-test/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/nominal-integration-test/" data-link-title="名義 Integration Test" data-link-desc="名稱含 integration 但核心依賴全用 fake 的 test，驗證內部狀態機而非真實服務互動">名義 integration test&lt;/a> 是指 test 的名稱或檔案路徑包含「integration」或「端對端」，但實際上核心外部依賴全部被 fake 替換，驗證的是內部狀態機而非真實服務互動。這類 test 的核心問題是命名造成的認知偏差：團隊以為「integration test 有寫」，實際上協議層完全沒被驗證。它們驗證的邏輯可能完全正確 — 問題在命名，不在品質。&lt;/p>
&lt;h2 id="辨識特徵">辨識特徵&lt;/h2>
&lt;p>app_tunnel 的 &lt;code>connection_flow_test.dart&lt;/code> 是具體案例。檔名標題是端對端整合測試，但內部使用了三個核心替身：&lt;code>FakeWebSocketChannel&lt;/code>、&lt;code>FakeBiometricService&lt;/code>、&lt;code>InMemoryCredentialRepository&lt;/code>（&lt;a href="https://tarrragon.github.io/blog/testing/cases/auth-handshake-missing-mock-blindspot/" data-link-title="T.C2 Auth handshake 邏輯缺失被 FakeWebSocketChannel 遮蔽" data-link-desc="ttyd 連線後需要發送 auth token JSON frame 完成認證，整個邏輯未實作 — FakeWebSocketChannel 的 ready 立即完成不需認證，test 永遠看到連線成功">T.C2&lt;/a>）。&lt;/p>
&lt;p>名義 integration test 有三個共同特徵可用來辨識。&lt;/p>
&lt;h3 id="特徵一核心外部依賴全替換">特徵一：核心外部依賴全替換&lt;/h3>
&lt;p>Integration test 的價值在於驗證程式碼與外部系統的互動邊界。如果所有外部依賴都被 fake 取代，test 驗證的實際上是「假設外部系統行為符合開發者預期，內部邏輯是否正確」。這和 unit test 的差別只在 scope 大小 — 多個內部元件一起測 — 不在驗證對象的本質。&lt;/p>
&lt;p>判斷方式：列出 test 的所有依賴注入點，計算有多少外部服務被替換成 fake。如果 100% 的外部依賴都是 fake，這個 test 不驗證任何真實互動。&lt;/p>
&lt;h3 id="特徵二沒有真實的-io-操作">特徵二：沒有真實的 I/O 操作&lt;/h3>
&lt;p>真正的 integration test 會產生真實的網路連線、讀寫真實的檔案、或呼叫真實的 API endpoint。名義 integration test 的所有 I/O 都在 process 內部完成 — &lt;code>StreamController&lt;/code> 替代網路 stream，&lt;code>Map&amp;lt;String, String&amp;gt;&lt;/code> 替代資料庫，&lt;code>Future.value()&lt;/code> 替代非同步 I/O。&lt;/p>
&lt;p>這些替身讓 test 執行速度快、結果穩定，但代價是完全跳過了 I/O 邊界上的所有行為差異。&lt;/p>
&lt;h3 id="特徵三沒有環境前置條件">特徵三：沒有環境前置條件&lt;/h3>
&lt;p>真正的 integration test 需要外部環境準備：啟動服務、建立連線、準備測試資料。名義 integration test 的 &lt;code>setUp()&lt;/code> 只建立 fake 物件，不啟動任何外部程序，不需要網路，可以在任何環境下執行。&lt;/p>
&lt;p>環境前置條件的缺席是一個實用的快速判斷訊號。如果 &lt;code>setUp()&lt;/code> 裡沒有 &lt;code>docker compose up&lt;/code>、&lt;code>Process.start&lt;/code>、&lt;code>HttpClient.connect&lt;/code> 之類的操作，這個 test 很可能不接觸真實外部服務。&lt;/p>
&lt;h2 id="名義-integration-test-造成的認知偏差">名義 integration test 造成的認知偏差&lt;/h2>
&lt;p>名義 integration test 的技術問題可以修正（改名或補寫真實 integration test），但它造成的認知偏差更難修正。&lt;/p>
&lt;p>當團隊看到 test suite 包含「integration test」資料夾且全部通過，決策者的推論是「integration 已經驗證過了」。這個推論在名義 integration test 下是錯的 — 協議層和環境層完全沒被驗證 — 但決策者沒有動機去檢查 test 的內部實作。&lt;/p>
&lt;p>app_tunnel 的 11 個 &lt;code>connection_flow_test&lt;/code> 全過，開發者合理認為「連線流程的整合測試已通過」。實際上這 11 個 test 驗證的是 &lt;code>ConnectionManager&lt;/code> 的內部狀態機在各種情境下的轉換正確性（斷線重連、錯誤處理、狀態回報），不是「和 ttyd 的連線流程是否正確」。Auth handshake 缺失直到實機測試才被發現。&lt;/p>
&lt;h2 id="修正策略">修正策略&lt;/h2>
&lt;h3 id="修正命名">修正命名&lt;/h3>
&lt;p>最低成本的修正是讓 test 名稱反映真實驗證對象。命名改動不影響 test 本身的價值 — 這些 test 驗證內部狀態機的邏輯仍然有用 — 只是消除命名造成的認知偏差。&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>connection_flow_test&lt;/td>
 &lt;td>connection_state_machine_test&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>integration_test/&lt;/td>
 &lt;td>state_machine_test/&lt;/td>
 &lt;td>資料夾名稱影響團隊對 test 覆蓋範圍的認知&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="補寫真實-integration-test">補寫真實 integration test&lt;/h3>
&lt;p>命名修正只消除誤解，不補上缺失的驗證層。如果服務的協議互動是關鍵路徑（連線、認證、資料交換），需要補寫對真實服務的 protocol integration test。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/testing/knowledge-cards/nominal-integration-test/" data-link-title="名義 Integration Test" data-link-desc="名稱含 integration 但核心依賴全用 fake 的 test，驗證內部狀態機而非真實服務互動">名義 integration test</a> 是指 test 的名稱或檔案路徑包含「integration」或「端對端」，但實際上核心外部依賴全部被 fake 替換，驗證的是內部狀態機而非真實服務互動。這類 test 的核心問題是命名造成的認知偏差：團隊以為「integration test 有寫」，實際上協議層完全沒被驗證。它們驗證的邏輯可能完全正確 — 問題在命名，不在品質。</p>
<h2 id="辨識特徵">辨識特徵</h2>
<p>app_tunnel 的 <code>connection_flow_test.dart</code> 是具體案例。檔名標題是端對端整合測試，但內部使用了三個核心替身：<code>FakeWebSocketChannel</code>、<code>FakeBiometricService</code>、<code>InMemoryCredentialRepository</code>（<a href="/blog/testing/cases/auth-handshake-missing-mock-blindspot/" data-link-title="T.C2 Auth handshake 邏輯缺失被 FakeWebSocketChannel 遮蔽" data-link-desc="ttyd 連線後需要發送 auth token JSON frame 完成認證，整個邏輯未實作 — FakeWebSocketChannel 的 ready 立即完成不需認證，test 永遠看到連線成功">T.C2</a>）。</p>
<p>名義 integration test 有三個共同特徵可用來辨識。</p>
<h3 id="特徵一核心外部依賴全替換">特徵一：核心外部依賴全替換</h3>
<p>Integration test 的價值在於驗證程式碼與外部系統的互動邊界。如果所有外部依賴都被 fake 取代，test 驗證的實際上是「假設外部系統行為符合開發者預期，內部邏輯是否正確」。這和 unit test 的差別只在 scope 大小 — 多個內部元件一起測 — 不在驗證對象的本質。</p>
<p>判斷方式：列出 test 的所有依賴注入點，計算有多少外部服務被替換成 fake。如果 100% 的外部依賴都是 fake，這個 test 不驗證任何真實互動。</p>
<h3 id="特徵二沒有真實的-io-操作">特徵二：沒有真實的 I/O 操作</h3>
<p>真正的 integration test 會產生真實的網路連線、讀寫真實的檔案、或呼叫真實的 API endpoint。名義 integration test 的所有 I/O 都在 process 內部完成 — <code>StreamController</code> 替代網路 stream，<code>Map&lt;String, String&gt;</code> 替代資料庫，<code>Future.value()</code> 替代非同步 I/O。</p>
<p>這些替身讓 test 執行速度快、結果穩定，但代價是完全跳過了 I/O 邊界上的所有行為差異。</p>
<h3 id="特徵三沒有環境前置條件">特徵三：沒有環境前置條件</h3>
<p>真正的 integration test 需要外部環境準備：啟動服務、建立連線、準備測試資料。名義 integration test 的 <code>setUp()</code> 只建立 fake 物件，不啟動任何外部程序，不需要網路，可以在任何環境下執行。</p>
<p>環境前置條件的缺席是一個實用的快速判斷訊號。如果 <code>setUp()</code> 裡沒有 <code>docker compose up</code>、<code>Process.start</code>、<code>HttpClient.connect</code> 之類的操作，這個 test 很可能不接觸真實外部服務。</p>
<h2 id="名義-integration-test-造成的認知偏差">名義 integration test 造成的認知偏差</h2>
<p>名義 integration test 的技術問題可以修正（改名或補寫真實 integration test），但它造成的認知偏差更難修正。</p>
<p>當團隊看到 test suite 包含「integration test」資料夾且全部通過，決策者的推論是「integration 已經驗證過了」。這個推論在名義 integration test 下是錯的 — 協議層和環境層完全沒被驗證 — 但決策者沒有動機去檢查 test 的內部實作。</p>
<p>app_tunnel 的 11 個 <code>connection_flow_test</code> 全過，開發者合理認為「連線流程的整合測試已通過」。實際上這 11 個 test 驗證的是 <code>ConnectionManager</code> 的內部狀態機在各種情境下的轉換正確性（斷線重連、錯誤處理、狀態回報），不是「和 ttyd 的連線流程是否正確」。Auth handshake 缺失直到實機測試才被發現。</p>
<h2 id="修正策略">修正策略</h2>
<h3 id="修正命名">修正命名</h3>
<p>最低成本的修正是讓 test 名稱反映真實驗證對象。命名改動不影響 test 本身的價值 — 這些 test 驗證內部狀態機的邏輯仍然有用 — 只是消除命名造成的認知偏差。</p>
<table>
  <thead>
      <tr>
          <th>原名稱</th>
          <th>修正後名稱</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>connection_flow_test</td>
          <td>connection_state_machine_test</td>
          <td>測的是狀態機邏輯，不是真實連線流程</td>
      </tr>
      <tr>
          <td>端對端整合測試</td>
          <td>狀態機分支覆蓋</td>
          <td>測的是分支覆蓋，不是端對端</td>
      </tr>
      <tr>
          <td>integration_test/</td>
          <td>state_machine_test/</td>
          <td>資料夾名稱影響團隊對 test 覆蓋範圍的認知</td>
      </tr>
  </tbody>
</table>
<h3 id="補寫真實-integration-test">補寫真實 integration test</h3>
<p>命名修正只消除誤解，不補上缺失的驗證層。如果服務的協議互動是關鍵路徑（連線、認證、資料交換），需要補寫對真實服務的 protocol integration test。</p>
<p>補寫的判斷原則不在本章展開 — 見 <a href="/blog/testing/01-test-strategy-layers/when-protocol-integration-test/" data-link-title="判斷原則：什麼時候需要 protocol integration test" data-link-desc="從服務架構特徵判斷是否需要 protocol integration test 的決策流程 — 協議複雜度、mock 寬鬆度、失敗靜默度三個維度">判斷原則：什麼時候需要 protocol integration test</a>。</p>
<h3 id="在-test-檔案內標明依賴替換清單">在 test 檔案內標明依賴替換清單</h3>
<p>在 test 檔案的頂部註釋中列出所有被 fake 取代的依賴，讓後續讀者不需要逐行追蹤就能判斷這個 test 的驗證邊界。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// Faked dependencies: WebSocketChannel, BiometricService, CredentialRepository
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// Verifies: ConnectionManager state machine transitions
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="n">Does</span> <span class="n">NOT</span> <span class="nl">verify:</span> <span class="n">real</span> <span class="n">WS</span> <span class="n">protocol</span><span class="p">,</span> <span class="n">auth</span> <span class="n">handshake</span><span class="p">,</span> <span class="n">biometric</span> <span class="n">hardware</span></span></span></code></pre></div><h2 id="下一步路由">下一步路由</h2>
<ul>
<li>判斷是否需要補寫真實 integration test → <a href="/blog/testing/01-test-strategy-layers/when-protocol-integration-test/" data-link-title="判斷原則：什麼時候需要 protocol integration test" data-link-desc="從服務架構特徵判斷是否需要 protocol integration test 的決策流程 — 協議複雜度、mock 寬鬆度、失敗靜默度三個維度">判斷原則：什麼時候需要 protocol integration test</a></li>
<li>Mock 遮蔽機制的完整分析 → <a href="/blog/testing/01-test-strategy-layers/mock-masking-mechanism/" 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/" data-link-title="模組三：協議整合測試" data-link-desc="對真實服務驗證 WebSocket / gRPC / HTTP 協議契約 — unit test 和 E2E test 之間的一層">模組三：協議整合測試</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></channel></rss>