<?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/tags/%E6%B8%AC%E8%A9%A6%E9%A9%85%E5%8B%95%E9%96%8B%E7%99%BC/</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>Wed, 04 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/%E6%B8%AC%E8%A9%A6%E9%A9%85%E5%8B%95%E9%96%8B%E7%99%BC/index.xml" rel="self" type="application/rss+xml"/><item><title>錯誤修復和重構方法論</title><link>https://tarrragon.github.io/blog/record/%E9%8C%AF%E8%AA%A4%E4%BF%AE%E5%BE%A9%E5%92%8C%E9%87%8D%E6%A7%8B%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E9%8C%AF%E8%AA%A4%E4%BF%AE%E5%BE%A9%E5%92%8C%E9%87%8D%E6%A7%8B%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;p>測試失敗了，應該修改程式，還是修改測試？&lt;/p>
&lt;p>這個判斷決定了整個修復方向。判斷錯，我們會花時間做出一個「讓測試通過」但實際上破壞需求的修改。&lt;/p></description><content:encoded><![CDATA[<p>測試失敗了，應該修改程式，還是修改測試？</p>
<p>這個判斷決定了整個修復方向。判斷錯，我們會花時間做出一個「讓測試通過」但實際上破壞需求的修改。</p>
<h2 id="核心原則程式服務測試測試服務需求">核心原則：程式服務測試，測試服務需求</h2>
<p>測試代表需求的具體描述。為了讓測試通過而修改測試本身，等於在需求上妥協。正確的做法是保持測試不變，調整程式實作直到符合期望。</p>
<p>唯一的例外：需求本身發生了變化——架構調整、業務流程重設計。這兩種情況的判斷，就是整個方法論的核心。</p>
<h2 id="第一步分類">第一步：分類</h2>
<p>面對測試失敗，先問「為什麼失敗」，不是「怎麼修」。</p>
<p><strong>程式實作錯誤</strong>：需求沒變，但程式行為不符預期——錯誤輸出、邏輯判斷有誤、型別處理不當。處理方式直接：保持測試不變，修正程式。</p>
<p>最容易犯的錯誤，是在這種情況下改了測試的期望值，讓測試配合錯誤的程式。這等於讓錯誤行為成為「正確需求」。</p>
<p><strong>架構變更需求</strong>：需求文件已更新，業務流程本質性改變，影響多個模組。這類情況確實需要調整測試，但前提是需求文件已反映這個變更。步驟：先確認文件、評估變更範圍、列出需修改的測試，最後才執行。跳過前置確認直接改測試，和無章法地亂改沒有區別。</p>
<h2 id="觀測公開行為不觀測內部實作">觀測公開行為，不觀測內部實作</h2>
<p>我們只驗證透過公開介面的輸入輸出、公開屬性的狀態變化。不該碰私有方法的調用順序、私有屬性的中間值。</p>
<p>一旦測試觀測內部狀態，它就和具體實作綁定了，任何重構都會讓測試失敗，即使行為沒變。</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">// 錯誤：觀測內部私有屬性
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="n">test</span><span class="p">(</span><span class="s1">&#39;書籍驗證應該檢查所有欄位&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="kd">final</span> <span class="n">validator</span> <span class="o">=</span> <span class="n">BookValidator</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">validator</span><span class="p">.</span><span class="n">validate</span><span class="p">(</span><span class="n">invalidBook</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="n">expect</span><span class="p">(</span><span class="n">validator</span><span class="p">.</span><span class="n">_titleValidated</span><span class="p">,</span> <span class="n">isTrue</span><span class="p">);</span>   <span class="c1">// 內部狀態
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>  <span class="n">expect</span><span class="p">(</span><span class="n">validator</span><span class="p">.</span><span class="n">_isbnValidated</span><span class="p">,</span> <span class="n">isTrue</span><span class="p">);</span>    <span class="c1">// 私有屬性
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="p">});</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// 正確：觀測公開行為結果
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="n">test</span><span class="p">(</span><span class="s1">&#39;書籍驗證應該檢查所有欄位&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="kd">final</span> <span class="n">validator</span> <span class="o">=</span> <span class="n">BookValidator</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="n">validator</span><span class="p">.</span><span class="n">validate</span><span class="p">(</span><span class="n">invalidBook</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">isValid</span><span class="p">,</span> <span class="n">isFalse</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">errors</span><span class="p">,</span> <span class="n">contains</span><span class="p">(</span><span class="s1">&#39;標題不可為空&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">errors</span><span class="p">,</span> <span class="n">contains</span><span class="p">(</span><span class="s1">&#39;ISBN 格式錯誤&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><h2 id="三個常見的反模式">三個常見的反模式</h2>
<p><strong>測試遷就程式</strong>：程式返回錯誤值，開發者把測試期望值改成那個錯誤值。測試通過了，bug 也永遠存在了。正確做法：保持期望值不動，修正程式邏輯。</p>
<p><strong>內部狀態依賴</strong>：測試直接存取 <code>book._internalState</code> 來驗證操作。一旦重構就失敗。改用公開方法 <code>book.isAvailable()</code> 驗證行為結果。</p>
<p><strong>跳過文件檢查</strong>：覺得需求「應該」要改，就直接改測試，沒確認需求文件是否更新。正確做法：先查規格書，確認文件已反映變更，再評估影響範圍，最後才動手。</p>
<h2 id="驗收標準">驗收標準</h2>
<p>修復前：錯誤類型已判斷、需求文件狀態已確認、受影響測試範圍已識別。</p>
<p>修復後：所有測試 100% 通過、無內部狀態曝露、無行為旁路、每個修改都有測試保護。</p>
<p>100% 通過率是底線。某個測試通過不了，不是跳過它或修改它，是找出真正的問題。</p>]]></content:encoded></item></channel></rss>