<?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>Unittest on Tarragon</title><link>https://tarrragon.github.io/blog/tags/unittest/</link><description>Recent content in Unittest on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 20 Jan 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/unittest/index.xml" rel="self" type="application/rss+xml"/><item><title>5.3 unittest 基礎</title><link>https://tarrragon.github.io/blog/python/05-error-testing/unittest/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/05-error-testing/unittest/</guid><description>&lt;p>&lt;code>unittest&lt;/code> 是 Python 內建的測試框架，提供了測試組織、斷言和測試執行等功能。Hook 系統的測試都使用 &lt;code>unittest&lt;/code> 撰寫。&lt;/p>
&lt;h2 id="基本結構">基本結構&lt;/h2>
&lt;h3 id="最簡單的測試">最簡單的測試&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">unittest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestCalculator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TestCase&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_subtract&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">main&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>執行測試：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">$ python -m unittest test_calculator.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">----------------------------------------------------------------------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">Ran &lt;span class="m">2&lt;/span> tests in 0.001s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">OK&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="測試類別結構">測試類別結構&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">unittest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestMyModule&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TestCase&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="nd">@classmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">setUpClass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;在所有測試前執行一次&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shared_resource&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_expensive_resource&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="nd">@classmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">tearDownClass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;在所有測試後執行一次&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shared_resource&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cleanup&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">setUp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;在每個測試前執行&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">test_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;key&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;value&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">tearDown&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;在每個測試後執行&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">test_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_something&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試方法必須以 test_ 開頭&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&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;code>assertEqual(a, b)&lt;/code>&lt;/td>
 &lt;td>a == b&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>assertNotEqual(a, b)&lt;/code>&lt;/td>
 &lt;td>a != b&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>assertTrue(x)&lt;/code>&lt;/td>
 &lt;td>x is True&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>assertFalse(x)&lt;/code>&lt;/td>
 &lt;td>x is False&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>assertIs(a, b)&lt;/code>&lt;/td>
 &lt;td>a is b&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>assertIsNone(x)&lt;/code>&lt;/td>
 &lt;td>x is None&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>assertIn(a, b)&lt;/code>&lt;/td>
 &lt;td>a in b&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>assertIsInstance(a, b)&lt;/code>&lt;/td>
 &lt;td>isinstance(a, b)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>assertRaises(Error)&lt;/code>&lt;/td>
 &lt;td>拋出指定異常&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="實際範例測試-hook-io">實際範例：測試 Hook IO&lt;/h2>
&lt;p>來自 &lt;code>.claude/lib/tests/test_hook_io.py&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">unittest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">io&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">StringIO&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">unittest.mock&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">patch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1"># 導入被測試的模組&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">hook_io&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 10&lt;/span>&lt;span class="cl"> &lt;span class="n">read_hook_input&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl"> &lt;span class="n">write_hook_output&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 12&lt;/span>&lt;span class="cl"> &lt;span class="n">create_pretooluse_output&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 13&lt;/span>&lt;span class="cl"> &lt;span class="n">create_posttooluse_output&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestReadHookInput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TestCase&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 18&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試 read_hook_input 函式&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 20&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_valid_json_input&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試有效的 JSON 輸入&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 22&lt;/span>&lt;span class="cl"> &lt;span class="n">test_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;tool_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Write&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;file_path&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/test.txt&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl"> &lt;span class="n">json_input&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">test_data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sys.stdin&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StringIO&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json_input&lt;/span>&lt;span class="p">)):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 26&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">read_hook_input&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 27&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 28&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">test_data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 30&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_invalid_json_returns_empty_dict&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 31&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試無效的 JSON 應返回空字典&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 32&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sys.stdin&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StringIO&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;not valid json&amp;#34;&lt;/span>&lt;span class="p">)):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 33&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">read_hook_input&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 35&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 36&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 37&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_empty_input_returns_empty_dict&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 38&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試空輸入應返回空字典&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sys.stdin&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StringIO&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">)):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 40&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">read_hook_input&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 41&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 42&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 44&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 45&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestWriteHookOutput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TestCase&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 46&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試 write_hook_output 函式&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_output_json_format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試輸出為有效的 JSON 格式&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl"> &lt;span class="n">test_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;decision&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;allow&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;reason&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;OK&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 51&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 52&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sys.stdout&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">new_callable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">StringIO&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">mock_stdout&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&lt;/span>&lt;span class="cl"> &lt;span class="n">write_hook_output&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">test_data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl"> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mock_stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getvalue&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&lt;/span>&lt;span class="cl"> &lt;span class="n">parsed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loads&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">output&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 57&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parsed&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;decision&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s2">&amp;#34;allow&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_chinese_characters_preserved&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試中文字元被保留&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 61&lt;/span>&lt;span class="cl"> &lt;span class="n">test_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;你好&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 62&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sys.stdout&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">new_callable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">StringIO&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">mock_stdout&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl"> &lt;span class="n">write_hook_output&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">test_data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ensure_ascii&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 65&lt;/span>&lt;span class="cl"> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mock_stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getvalue&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertIn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;你好&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 68&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 69&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestCreatePretoolueOutput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TestCase&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試 create_pretooluse_output 函式&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_basic_output_structure&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試基本輸出結構&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_pretooluse_output&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 76&lt;/span>&lt;span class="cl"> &lt;span class="n">decision&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;allow&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 77&lt;/span>&lt;span class="cl"> &lt;span class="n">reason&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Test reason&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertIn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;hookSpecificOutput&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hookSpecificOutput&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;permissionDecision&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;allow&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 84&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_with_user_prompt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試包含 userPrompt 的輸出&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_pretooluse_output&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 89&lt;/span>&lt;span class="cl"> &lt;span class="n">decision&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;ask&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 90&lt;/span>&lt;span class="cl"> &lt;span class="n">reason&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Need confirmation&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl"> &lt;span class="n">user_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Continue?&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 94&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hookSpecificOutput&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;userPrompt&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Continue?&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 99&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&lt;/span>&lt;span class="cl"> &lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">main&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="測試異常">測試異常&lt;/h2>
&lt;h3 id="assertraises">assertRaises&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_raises_value_error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試函式是否拋出 ValueError&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertRaises&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ne">ValueError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;not a number&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_raises_with_message&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試異常訊息&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertRaises&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ne">ValueError&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;invalid input&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertIn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;invalid&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exception&lt;/span>&lt;span class="p">))&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="測試檔案操作">測試檔案操作&lt;/h2>
&lt;h3 id="使用臨時檔案">使用臨時檔案&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">tempfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">unittest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestFileOperations&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TestCase&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_read_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試檔案讀取&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">tempfile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">NamedTemporaryFile&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">mode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">suffix&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;.txt&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">delete&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">f&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test content&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">temp_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">read_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">temp_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;test content&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">finally&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">unlink&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">temp_path&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="使用臨時目錄">使用臨時目錄&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">tempfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">unittest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestDirectoryOperations&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TestCase&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">setUp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">temp_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">tempfile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mkdtemp&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">tearDown&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">shutil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">shutil&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rmtree&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">temp_dir&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_create_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">file_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">temp_dir&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;test.txt&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">create_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file_path&lt;/span>&lt;span class="p">))&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="執行測試">執行測試&lt;/h2>
&lt;h3 id="執行單一測試檔案">執行單一測試檔案&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">python -m unittest tests/test_hook_io.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="執行單一測試類別">執行單一測試類別&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">python -m unittest tests.test_hook_io.TestReadHookInput&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="執行單一測試方法">執行單一測試方法&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">python -m unittest tests.test_hook_io.TestReadHookInput.test_valid_json_input&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="執行所有測試">執行所有測試&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">python -m unittest discover -s tests -p &lt;span class="s2">&amp;#34;test_*.py&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="詳細輸出">詳細輸出&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">python -m unittest -v tests/test_hook_io.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="測試組織">測試組織&lt;/h2>
&lt;h3 id="目錄結構">目錄結構&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">.claude/lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">├── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">├── git_utils.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">├── hook_io.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">├── hook_logging.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">└── tests/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> ├── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> ├── test_git_utils.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> ├── test_hook_io.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> └── test_hook_logging.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="命名慣例">命名慣例&lt;/h3>
&lt;ul>
&lt;li>測試檔案：&lt;code>test_&amp;lt;module&amp;gt;.py&lt;/code>&lt;/li>
&lt;li>測試類別：&lt;code>Test&amp;lt;ClassName&amp;gt;&lt;/code>&lt;/li>
&lt;li>測試方法：&lt;code>test_&amp;lt;behavior&amp;gt;&lt;/code>&lt;/li>
&lt;/ul>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># test_git_utils.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestRunGitCommand&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TestCase&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_successful_command_returns_true&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_failed_command_returns_false&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_timeout_returns_error_message&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="最佳實踐">最佳實踐&lt;/h2>
&lt;h3 id="1-一個測試驗證一件事">1. 一個測試驗證一件事&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 好：每個測試只驗證一個行為&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_valid_input_returns_true&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;valid&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_invalid_input_returns_false&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;invalid&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertFalse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 不好：一個測試驗證多件事&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;valid&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertFalse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;invalid&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-使用描述性的測試名稱">2. 使用描述性的測試名稱&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 好：清楚說明測試內容&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_empty_input_returns_empty_dict&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 不好：模糊的名稱&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_input&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-使用-setup-避免重複">3. 使用 setUp 避免重複&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestMarkdownChecker&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TestCase&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">setUp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">checker&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MarkdownLinkChecker&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">test_content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;# Test&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">[link](/python/05-error-testing/unittest/file.md)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_check_valid_link&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 使用 setUp 中建立的物件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">checker&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">test_content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="思考題">思考題&lt;/h2>
&lt;ol>
&lt;li>&lt;code>setUp&lt;/code> 和 &lt;code>setUpClass&lt;/code> 有什麼區別？什麼時候用哪個？&lt;/li>
&lt;li>為什麼測試方法必須以 &lt;code>test_&lt;/code> 開頭？&lt;/li>
&lt;li>如何測試一個需要讀取 stdin 的函式？&lt;/li>
&lt;/ol>
&lt;h2 id="實作練習">實作練習&lt;/h2>
&lt;ol>
&lt;li>為 &lt;code>get_current_branch()&lt;/code> 函式撰寫測試&lt;/li>
&lt;li>測試一個會拋出異常的函式&lt;/li>
&lt;li>使用 &lt;code>unittest.skip&lt;/code> 暫時跳過某個測試&lt;/li>
&lt;/ol>
&lt;hr>
&lt;p>&lt;em>上一章：&lt;a href="https://tarrragon.github.io/blog/python/05-error-testing/exception/" data-link-title="5.1 異常處理策略" data-link-desc="何時捕獲、何時拋出">異常處理策略&lt;/a>&lt;/em>
&lt;em>下一章：&lt;a href="https://tarrragon.github.io/blog/python/05-error-testing/mock/" data-link-title="5.4 Mock 與測試隔離" data-link-desc="隔離外部依賴">Mock 與測試隔離&lt;/a>&lt;/em>&lt;/p></description><content:encoded><![CDATA[<p><code>unittest</code> 是 Python 內建的測試框架，提供了測試組織、斷言和測試執行等功能。Hook 系統的測試都使用 <code>unittest</code> 撰寫。</p>
<h2 id="基本結構">基本結構</h2>
<h3 id="最簡單的測試">最簡單的測試</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">unittest</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">TestCalculator</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="nf">test_add</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="nf">test_subtract</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="mi">5</span> <span class="o">-</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">unittest</span><span class="o">.</span><span class="n">main</span><span class="p">()</span></span></span></code></pre></div><p>執行測試：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">$ python -m unittest test_calculator.py
</span></span><span class="line"><span class="ln">2</span><span class="cl">..
</span></span><span class="line"><span class="ln">3</span><span class="cl">----------------------------------------------------------------------
</span></span><span class="line"><span class="ln">4</span><span class="cl">Ran <span class="m">2</span> tests in 0.001s
</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">OK</span></span></code></pre></div><h2 id="測試類別結構">測試類別結構</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">unittest</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">TestMyModule</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nd">@classmethod</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">def</span> <span class="nf">setUpClass</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="s2">&#34;&#34;&#34;在所有測試前執行一次&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">cls</span><span class="o">.</span><span class="n">shared_resource</span> <span class="o">=</span> <span class="n">create_expensive_resource</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="nd">@classmethod</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">def</span> <span class="nf">tearDownClass</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="s2">&#34;&#34;&#34;在所有測試後執行一次&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="bp">cls</span><span class="o">.</span><span class="n">shared_resource</span><span class="o">.</span><span class="n">cleanup</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="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;&#34;&#34;在每個測試前執行&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">test_data</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;value&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">def</span> <span class="nf">tearDown</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="s2">&#34;&#34;&#34;在每個測試後執行&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">test_data</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">def</span> <span class="nf">test_something</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試方法必須以 test_ 開頭&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertTrue</span><span class="p">(</span><span class="kc">True</span><span class="p">)</span></span></span></code></pre></div><h2 id="常用斷言方法">常用斷言方法</h2>
<table>
  <thead>
      <tr>
          <th>方法</th>
          <th>檢查</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>assertEqual(a, b)</code></td>
          <td>a == b</td>
      </tr>
      <tr>
          <td><code>assertNotEqual(a, b)</code></td>
          <td>a != b</td>
      </tr>
      <tr>
          <td><code>assertTrue(x)</code></td>
          <td>x is True</td>
      </tr>
      <tr>
          <td><code>assertFalse(x)</code></td>
          <td>x is False</td>
      </tr>
      <tr>
          <td><code>assertIs(a, b)</code></td>
          <td>a is b</td>
      </tr>
      <tr>
          <td><code>assertIsNone(x)</code></td>
          <td>x is None</td>
      </tr>
      <tr>
          <td><code>assertIn(a, b)</code></td>
          <td>a in b</td>
      </tr>
      <tr>
          <td><code>assertIsInstance(a, b)</code></td>
          <td>isinstance(a, b)</td>
      </tr>
      <tr>
          <td><code>assertRaises(Error)</code></td>
          <td>拋出指定異常</td>
      </tr>
  </tbody>
</table>
<h2 id="實際範例測試-hook-io">實際範例：測試 Hook IO</h2>
<p>來自 <code>.claude/lib/tests/test_hook_io.py</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="kn">import</span> <span class="nn">unittest</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="kn">from</span> <span class="nn">io</span> <span class="kn">import</span> <span class="n">StringIO</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">patch</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl">
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="c1"># 導入被測試的模組</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">hook_io</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="n">read_hook_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="n">write_hook_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="n">create_pretooluse_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">create_posttooluse_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="k">class</span> <span class="nc">TestReadHookInput</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試 read_hook_input 函式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="k">def</span> <span class="nf">test_valid_json_input</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試有效的 JSON 輸入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="n">test_data</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="s2">&#34;Write&#34;</span><span class="p">,</span> <span class="s2">&#34;file_path&#34;</span><span class="p">:</span> <span class="s2">&#34;/test.txt&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">        <span class="n">json_input</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">test_data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">        <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;sys.stdin&#34;</span><span class="p">,</span> <span class="n">StringIO</span><span class="p">(</span><span class="n">json_input</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">read_hook_input</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">test_data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="k">def</span> <span class="nf">test_invalid_json_returns_empty_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試無效的 JSON 應返回空字典&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">        <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;sys.stdin&#34;</span><span class="p">,</span> <span class="n">StringIO</span><span class="p">(</span><span class="s2">&#34;not valid json&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">read_hook_input</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="p">{})</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="k">def</span> <span class="nf">test_empty_input_returns_empty_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試空輸入應返回空字典&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;sys.stdin&#34;</span><span class="p">,</span> <span class="n">StringIO</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">read_hook_input</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="p">{})</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="k">class</span> <span class="nc">TestWriteHookOutput</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試 write_hook_output 函式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">    <span class="k">def</span> <span class="nf">test_output_json_format</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試輸出為有效的 JSON 格式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="n">test_data</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;decision&#34;</span><span class="p">:</span> <span class="s2">&#34;allow&#34;</span><span class="p">,</span> <span class="s2">&#34;reason&#34;</span><span class="p">:</span> <span class="s2">&#34;OK&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;sys.stdout&#34;</span><span class="p">,</span> <span class="n">new_callable</span><span class="o">=</span><span class="n">StringIO</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_stdout</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">            <span class="n">write_hook_output</span><span class="p">(</span><span class="n">test_data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">            <span class="n">output</span> <span class="o">=</span> <span class="n">mock_stdout</span><span class="o">.</span><span class="n">getvalue</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="n">parsed</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">parsed</span><span class="p">[</span><span class="s2">&#34;decision&#34;</span><span class="p">],</span> <span class="s2">&#34;allow&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="k">def</span> <span class="nf">test_chinese_characters_preserved</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試中文字元被保留&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="n">test_data</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;你好&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;sys.stdout&#34;</span><span class="p">,</span> <span class="n">new_callable</span><span class="o">=</span><span class="n">StringIO</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_stdout</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">            <span class="n">write_hook_output</span><span class="p">(</span><span class="n">test_data</span><span class="p">,</span> <span class="n">ensure_ascii</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">            <span class="n">output</span> <span class="o">=</span> <span class="n">mock_stdout</span><span class="o">.</span><span class="n">getvalue</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertIn</span><span class="p">(</span><span class="s2">&#34;你好&#34;</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="k">class</span> <span class="nc">TestCreatePretoolueOutput</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試 create_pretooluse_output 函式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="k">def</span> <span class="nf">test_basic_output_structure</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試基本輸出結構&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">create_pretooluse_output</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="n">decision</span><span class="o">=</span><span class="s2">&#34;allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">            <span class="n">reason</span><span class="o">=</span><span class="s2">&#34;Test reason&#34;</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertIn</span><span class="p">(</span><span class="s2">&#34;hookSpecificOutput&#34;</span><span class="p">,</span> <span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">            <span class="n">result</span><span class="p">[</span><span class="s2">&#34;hookSpecificOutput&#34;</span><span class="p">][</span><span class="s2">&#34;permissionDecision&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="s2">&#34;allow&#34;</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="k">def</span> <span class="nf">test_with_user_prompt</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試包含 userPrompt 的輸出&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">create_pretooluse_output</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">            <span class="n">decision</span><span class="o">=</span><span class="s2">&#34;ask&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">            <span class="n">reason</span><span class="o">=</span><span class="s2">&#34;Need confirmation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">            <span class="n">user_prompt</span><span class="o">=</span><span class="s2">&#34;Continue?&#34;</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">            <span class="n">result</span><span class="p">[</span><span class="s2">&#34;hookSpecificOutput&#34;</span><span class="p">][</span><span class="s2">&#34;userPrompt&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="s2">&#34;Continue?&#34;</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">
</span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">    <span class="n">unittest</span><span class="o">.</span><span class="n">main</span><span class="p">()</span></span></span></code></pre></div><h2 id="測試異常">測試異常</h2>
<h3 id="assertraises">assertRaises</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">test_raises_value_error</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試函式是否拋出 ValueError&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">with</span> <span class="bp">self</span><span class="o">.</span><span class="n">assertRaises</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="nb">int</span><span class="p">(</span><span class="s2">&#34;not a number&#34;</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="k">def</span> <span class="nf">test_raises_with_message</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試異常訊息&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">with</span> <span class="bp">self</span><span class="o">.</span><span class="n">assertRaises</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">)</span> <span class="k">as</span> <span class="n">context</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;invalid input&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">assertIn</span><span class="p">(</span><span class="s2">&#34;invalid&#34;</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">context</span><span class="o">.</span><span class="n">exception</span><span class="p">))</span></span></span></code></pre></div><h2 id="測試檔案操作">測試檔案操作</h2>
<h3 id="使用臨時檔案">使用臨時檔案</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">tempfile</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">unittest</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">TestFileOperations</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</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="k">def</span> <span class="nf">test_read_file</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試檔案讀取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">with</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">NamedTemporaryFile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="n">mode</span><span class="o">=</span><span class="s2">&#34;w&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="n">suffix</span><span class="o">=</span><span class="s2">&#34;.txt&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="n">delete</span><span class="o">=</span><span class="kc">False</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">&#34;test content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">temp_path</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">name</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">read_file</span><span class="p">(</span><span class="n">temp_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="s2">&#34;test content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">os</span><span class="o">.</span><span class="n">unlink</span><span class="p">(</span><span class="n">temp_path</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用臨時目錄">使用臨時目錄</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">tempfile</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">unittest</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">TestDirectoryOperations</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</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="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">temp_dir</span> <span class="o">=</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">mkdtemp</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="nf">tearDown</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="kn">import</span> <span class="nn">shutil</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">shutil</span><span class="o">.</span><span class="n">rmtree</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">temp_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="nf">test_create_file</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">file_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">temp_dir</span><span class="p">,</span> <span class="s2">&#34;test.txt&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">create_file</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertTrue</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">file_path</span><span class="p">))</span></span></span></code></pre></div><h2 id="執行測試">執行測試</h2>
<h3 id="執行單一測試檔案">執行單一測試檔案</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">python -m unittest tests/test_hook_io.py</span></span></code></pre></div><h3 id="執行單一測試類別">執行單一測試類別</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">python -m unittest tests.test_hook_io.TestReadHookInput</span></span></code></pre></div><h3 id="執行單一測試方法">執行單一測試方法</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">python -m unittest tests.test_hook_io.TestReadHookInput.test_valid_json_input</span></span></code></pre></div><h3 id="執行所有測試">執行所有測試</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">python -m unittest discover -s tests -p <span class="s2">&#34;test_*.py&#34;</span></span></span></code></pre></div><h3 id="詳細輸出">詳細輸出</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">python -m unittest -v tests/test_hook_io.py</span></span></code></pre></div><h2 id="測試組織">測試組織</h2>
<h3 id="目錄結構">目錄結構</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">.claude/lib/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── __init__.py
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── git_utils.py
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── hook_io.py
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">├── hook_logging.py
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">└── tests/
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    ├── __init__.py
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    ├── test_git_utils.py
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    ├── test_hook_io.py
</span></span><span class="line"><span class="ln">10</span><span class="cl">    └── test_hook_logging.py</span></span></code></pre></div><h3 id="命名慣例">命名慣例</h3>
<ul>
<li>測試檔案：<code>test_&lt;module&gt;.py</code></li>
<li>測試類別：<code>Test&lt;ClassName&gt;</code></li>
<li>測試方法：<code>test_&lt;behavior&gt;</code></li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># test_git_utils.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">class</span> <span class="nc">TestRunGitCommand</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">def</span> <span class="nf">test_successful_command_returns_true</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">def</span> <span class="nf">test_failed_command_returns_false</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">def</span> <span class="nf">test_timeout_returns_error_message</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">        <span class="o">...</span></span></span></code></pre></div><h2 id="最佳實踐">最佳實踐</h2>
<h3 id="1-一個測試驗證一件事">1. 一個測試驗證一件事</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><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="k">def</span> <span class="nf">test_valid_input_returns_true</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">validate</span><span class="p">(</span><span class="s2">&#34;valid&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">assertTrue</span><span class="p">(</span><span class="n">result</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="k">def</span> <span class="nf">test_invalid_input_returns_false</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">validate</span><span class="p">(</span><span class="s2">&#34;invalid&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">assertFalse</span><span class="p">(</span><span class="n">result</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="k">def</span> <span class="nf">test_validate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">assertTrue</span><span class="p">(</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;valid&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">assertFalse</span><span class="p">(</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;invalid&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">),</span> <span class="kc">None</span><span class="p">)</span></span></span></code></pre></div><h3 id="2-使用描述性的測試名稱">2. 使用描述性的測試名稱</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><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="k">def</span> <span class="nf">test_empty_input_returns_empty_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 不好：模糊的名稱</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">def</span> <span class="nf">test_input</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="o">...</span></span></span></code></pre></div><h3 id="3-使用-setup-避免重複">3. 使用 setUp 避免重複</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">TestMarkdownChecker</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">checker</span> <span class="o">=</span> <span class="n">MarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">test_content</span> <span class="o">=</span> <span class="s2">&#34;# Test</span><span class="se">\n</span><span class="s2">[link](/python/05-error-testing/unittest/file.md)&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">test_check_valid_link</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="c1"># 使用 setUp 中建立的物件</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">checker</span><span class="o">.</span><span class="n">check</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">test_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="o">...</span></span></span></code></pre></div><h2 id="思考題">思考題</h2>
<ol>
<li><code>setUp</code> 和 <code>setUpClass</code> 有什麼區別？什麼時候用哪個？</li>
<li>為什麼測試方法必須以 <code>test_</code> 開頭？</li>
<li>如何測試一個需要讀取 stdin 的函式？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>為 <code>get_current_branch()</code> 函式撰寫測試</li>
<li>測試一個會拋出異常的函式</li>
<li>使用 <code>unittest.skip</code> 暫時跳過某個測試</li>
</ol>
<hr>
<p><em>上一章：<a href="/blog/python/05-error-testing/exception/" data-link-title="5.1 異常處理策略" data-link-desc="何時捕獲、何時拋出">異常處理策略</a></em>
<em>下一章：<a href="/blog/python/05-error-testing/mock/" data-link-title="5.4 Mock 與測試隔離" data-link-desc="隔離外部依賴">Mock 與測試隔離</a></em></p>
]]></content:encoded></item><item><title>5.4 Mock 與測試隔離</title><link>https://tarrragon.github.io/blog/python/05-error-testing/mock/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/05-error-testing/mock/</guid><description>&lt;p>當測試的程式碼依賴外部資源（檔案系統、網路、stdin/stdout）時，我們需要使用 Mock 來隔離這些依賴，確保測試的可靠性和速度。&lt;/p>
&lt;h2 id="為什麼需要-mock">為什麼需要 Mock？&lt;/h2>
&lt;h3 id="問題場景">問題場景&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">read_hook_input&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;從 stdin 讀取 JSON 輸入&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdin&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 測試時如何提供 stdin？&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="使用-mock-解決">使用 Mock 解決&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">unittest.mock&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">patch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">io&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">StringIO&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_read_hook_input&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">json_input&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;{&amp;#34;key&amp;#34;: &amp;#34;value&amp;#34;}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 用 StringIO 替換 sys.stdin&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sys.stdin&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StringIO&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json_input&lt;/span>&lt;span class="p">)):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">read_hook_input&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">assert&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;key&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;value&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="unittestmock-基礎">unittest.mock 基礎&lt;/h2>
&lt;h3 id="patch-裝飾器">patch 裝飾器&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">unittest.mock&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">patch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestMyFunction&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TestCase&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="nd">@patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;module.function_to_mock&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_something&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mock_func&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">return_value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;mocked result&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">my_function&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;expected&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="patch-上下文管理器">patch 上下文管理器&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_something&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;module.function&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">mock_func&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">return_value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;mocked&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">my_function&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;expected&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="實際範例測試-hook-io">實際範例：測試 Hook IO&lt;/h2>
&lt;p>來自 &lt;code>.claude/lib/tests/test_hook_io.py&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">unittest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">io&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">StringIO&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">unittest.mock&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">patch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">hook_io&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">read_hook_input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">write_hook_output&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestReadHookInput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TestCase&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試 read_hook_input 函式&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_valid_json_input&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試有效的 JSON 輸入&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">test_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;tool_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Write&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;file_path&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/test.txt&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">json_input&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">test_data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Mock sys.stdin&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sys.stdin&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StringIO&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json_input&lt;/span>&lt;span class="p">)):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">read_hook_input&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">test_data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_invalid_json_returns_empty_dict&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試無效的 JSON&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sys.stdin&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StringIO&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;not valid json&amp;#34;&lt;/span>&lt;span class="p">)):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">read_hook_input&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestWriteHookOutput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TestCase&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試 write_hook_output 函式&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_output_json_format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試輸出為有效的 JSON&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="n">test_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;decision&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;allow&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Mock sys.stdout&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sys.stdout&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">new_callable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">StringIO&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">mock_stdout&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="n">write_hook_output&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">test_data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mock_stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getvalue&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 驗證輸出是有效的 JSON&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="n">parsed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loads&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">output&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parsed&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;decision&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s2">&amp;#34;allow&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_chinese_preserved&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試中文字元被保留&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="n">test_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;你好&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sys.stdout&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">new_callable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">StringIO&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">mock_stdout&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="n">write_hook_output&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">test_data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ensure_ascii&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mock_stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getvalue&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertIn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;你好&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="mock-物件的設定">Mock 物件的設定&lt;/h2>
&lt;h3 id="return_value">return_value&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">unittest.mock&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Mock&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Mock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">return_value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">42&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mock_func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 42&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="side_effect---動態返回值">side_effect - 動態返回值&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">unittest.mock&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Mock&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Mock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 依序返回不同值&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">side_effect&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="c1"># 根據輸入返回不同值&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">side_effect_func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">side_effect&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">side_effect_func&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 10&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="side_effect---拋出異常">side_effect - 拋出異常&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">unittest.mock&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Mock&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Mock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">side_effect&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Error!&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 拋出 ValueError&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="驗證-mock-被呼叫">驗證 Mock 被呼叫&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">unittest.mock&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Mock&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">call&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Mock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 呼叫 mock&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;value&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1"># 驗證呼叫&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assert_called&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 被呼叫過&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assert_called_once&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 只被呼叫一次（這會失敗）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assert_called_with&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 最後一次呼叫的參數&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="c1"># 檢查所有呼叫&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="n">mock_func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assert_has_calls&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">call&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;value&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">call&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="c1"># 呼叫次數&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mock_func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">call_count&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="實際範例測試-git-工具">實際範例：測試 Git 工具&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">unittest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">unittest.mock&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">patch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Mock&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">get_current_branch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestRunGitCommand&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unittest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TestCase&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="nd">@patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;subprocess.run&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_successful_command&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mock_run&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試成功的 git 命令&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 設定 mock 返回值&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Mock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">returncode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;main&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stderr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_run&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">return_value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mock_result&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--show-current&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">success&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">output&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;main&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 驗證 subprocess.run 被正確呼叫&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_run&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assert_called_once&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">call_args&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mock_run&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">call_args&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">call_args&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;git&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--show-current&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="nd">@patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;subprocess.run&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_failed_command&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mock_run&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試失敗的 git 命令&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Mock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">returncode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stderr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;fatal: not a git repository&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_run&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">return_value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mock_result&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertFalse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">success&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertIn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;not a git repository&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="nd">@patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;subprocess.run&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_timeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mock_run&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試命令超時&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">subprocess&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="n">mock_run&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">side_effect&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TimeoutExpired&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;git&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertFalse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">success&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assertIn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;timed out&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="magicmock">MagicMock&lt;/h2>
&lt;p>&lt;code>MagicMock&lt;/code> 自動支援魔術方法：&lt;/p></description><content:encoded><![CDATA[<p>當測試的程式碼依賴外部資源（檔案系統、網路、stdin/stdout）時，我們需要使用 Mock 來隔離這些依賴，確保測試的可靠性和速度。</p>
<h2 id="為什麼需要-mock">為什麼需要 Mock？</h2>
<h3 id="問題場景">問題場景</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">read_hook_input</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;從 stdin 讀取 JSON 輸入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 測試時如何提供 stdin？</span></span></span></code></pre></div><h3 id="使用-mock-解決">使用 Mock 解決</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">patch</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">io</span> <span class="kn">import</span> <span class="n">StringIO</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">test_read_hook_input</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">json_input</span> <span class="o">=</span> <span class="s1">&#39;{&#34;key&#34;: &#34;value&#34;}&#39;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># 用 StringIO 替換 sys.stdin</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;sys.stdin&#34;</span><span class="p">,</span> <span class="n">StringIO</span><span class="p">(</span><span class="n">json_input</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">read_hook_input</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">assert</span> <span class="n">result</span> <span class="o">==</span> <span class="p">{</span><span class="s2">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;value&#34;</span><span class="p">}</span></span></span></code></pre></div><h2 id="unittestmock-基礎">unittest.mock 基礎</h2>
<h3 id="patch-裝飾器">patch 裝飾器</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">patch</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">class</span> <span class="nc">TestMyFunction</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nd">@patch</span><span class="p">(</span><span class="s2">&#34;module.function_to_mock&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">def</span> <span class="nf">test_something</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">mock_func</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="n">mock_func</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="s2">&#34;mocked result&#34;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">my_function</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="s2">&#34;expected&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="patch-上下文管理器">patch 上下文管理器</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">test_something</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;module.function&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_func</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="n">mock_func</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="s2">&#34;mocked&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">my_function</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="s2">&#34;expected&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="實際範例測試-hook-io">實際範例：測試 Hook IO</h2>
<p>來自 <code>.claude/lib/tests/test_hook_io.py</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">unittest</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">io</span> <span class="kn">import</span> <span class="n">StringIO</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">patch</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="kn">from</span> <span class="nn">hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span><span class="p">,</span> <span class="n">write_hook_output</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">class</span> <span class="nc">TestReadHookInput</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試 read_hook_input 函式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="nf">test_valid_json_input</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試有效的 JSON 輸入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">test_data</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="s2">&#34;Write&#34;</span><span class="p">,</span> <span class="s2">&#34;file_path&#34;</span><span class="p">:</span> <span class="s2">&#34;/test.txt&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">json_input</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">test_data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="c1"># Mock sys.stdin</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;sys.stdin&#34;</span><span class="p">,</span> <span class="n">StringIO</span><span class="p">(</span><span class="n">json_input</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">read_hook_input</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">test_data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">def</span> <span class="nf">test_invalid_json_returns_empty_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試無效的 JSON&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;sys.stdin&#34;</span><span class="p">,</span> <span class="n">StringIO</span><span class="p">(</span><span class="s2">&#34;not valid json&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">read_hook_input</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="p">{})</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">class</span> <span class="nc">TestWriteHookOutput</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試 write_hook_output 函式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="k">def</span> <span class="nf">test_output_json_format</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試輸出為有效的 JSON&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">test_data</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;decision&#34;</span><span class="p">:</span> <span class="s2">&#34;allow&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="c1"># Mock sys.stdout</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;sys.stdout&#34;</span><span class="p">,</span> <span class="n">new_callable</span><span class="o">=</span><span class="n">StringIO</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_stdout</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="n">write_hook_output</span><span class="p">(</span><span class="n">test_data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="n">output</span> <span class="o">=</span> <span class="n">mock_stdout</span><span class="o">.</span><span class="n">getvalue</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="c1"># 驗證輸出是有效的 JSON</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="n">parsed</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">parsed</span><span class="p">[</span><span class="s2">&#34;decision&#34;</span><span class="p">],</span> <span class="s2">&#34;allow&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="k">def</span> <span class="nf">test_chinese_preserved</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試中文字元被保留&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="n">test_data</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;你好&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;sys.stdout&#34;</span><span class="p">,</span> <span class="n">new_callable</span><span class="o">=</span><span class="n">StringIO</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_stdout</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="n">write_hook_output</span><span class="p">(</span><span class="n">test_data</span><span class="p">,</span> <span class="n">ensure_ascii</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">            <span class="n">output</span> <span class="o">=</span> <span class="n">mock_stdout</span><span class="o">.</span><span class="n">getvalue</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertIn</span><span class="p">(</span><span class="s2">&#34;你好&#34;</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span></span></span></code></pre></div><h2 id="mock-物件的設定">Mock 物件的設定</h2>
<h3 id="return_value">return_value</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">Mock</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">mock_func</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">mock_func</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="mi">42</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">result</span> <span class="o">=</span> <span class="n">mock_func</span><span class="p">()</span>  <span class="c1"># 42</span></span></span></code></pre></div><h3 id="side_effect---動態返回值">side_effect - 動態返回值</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">Mock</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">mock_func</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 依序返回不同值</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">mock_func</span><span class="o">.</span><span class="n">side_effect</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">mock_func</span><span class="p">()</span>  <span class="c1"># 1</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">mock_func</span><span class="p">()</span>  <span class="c1"># 2</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">mock_func</span><span class="p">()</span>  <span class="c1"># 3</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 根據輸入返回不同值</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">def</span> <span class="nf">side_effect_func</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</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">mock_func</span><span class="o">.</span><span class="n">side_effect</span> <span class="o">=</span> <span class="n">side_effect_func</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">mock_func</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>  <span class="c1"># 10</span></span></span></code></pre></div><h3 id="side_effect---拋出異常">side_effect - 拋出異常</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">Mock</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">mock_func</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">mock_func</span><span class="o">.</span><span class="n">side_effect</span> <span class="o">=</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;Error!&#34;</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">mock_func</span><span class="p">()</span>  <span class="c1"># 拋出 ValueError</span></span></span></code></pre></div><h2 id="驗證-mock-被呼叫">驗證 Mock 被呼叫</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">Mock</span><span class="p">,</span> <span class="n">call</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">mock_func</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 呼叫 mock</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">mock_func</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="s2">&#34;value&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">mock_func</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 驗證呼叫</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">mock_func</span><span class="o">.</span><span class="n">assert_called</span><span class="p">()</span>              <span class="c1"># 被呼叫過</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">mock_func</span><span class="o">.</span><span class="n">assert_called_once</span><span class="p">()</span>         <span class="c1"># 只被呼叫一次（這會失敗）</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">mock_func</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>     <span class="c1"># 最後一次呼叫的參數</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 檢查所有呼叫</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">mock_func</span><span class="o">.</span><span class="n">assert_has_calls</span><span class="p">([</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">call</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="s2">&#34;value&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">call</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">])</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># 呼叫次數</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">mock_func</span><span class="o">.</span><span class="n">call_count</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span></span></span></code></pre></div><h2 id="實際範例測試-git-工具">實際範例：測試 Git 工具</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">unittest</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">patch</span><span class="p">,</span> <span class="n">Mock</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">run_git_command</span><span class="p">,</span> <span class="n">get_current_branch</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></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">class</span> <span class="nc">TestRunGitCommand</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nd">@patch</span><span class="p">(</span><span class="s2">&#34;subprocess.run&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">test_successful_command</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">mock_run</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試成功的 git 命令&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="c1"># 設定 mock 返回值</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">mock_result</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">mock_result</span><span class="o">.</span><span class="n">returncode</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">mock_result</span><span class="o">.</span><span class="n">stdout</span> <span class="o">=</span> <span class="s2">&#34;main</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">mock_result</span><span class="o">.</span><span class="n">stderr</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">mock_run</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="n">mock_result</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertTrue</span><span class="p">(</span><span class="n">success</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">output</span><span class="p">,</span> <span class="s2">&#34;main&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># 驗證 subprocess.run 被正確呼叫</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">mock_run</span><span class="o">.</span><span class="n">assert_called_once</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">call_args</span> <span class="o">=</span> <span class="n">mock_run</span><span class="o">.</span><span class="n">call_args</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">call_args</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">],</span> <span class="p">[</span><span class="s2">&#34;git&#34;</span><span class="p">,</span> <span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="nd">@patch</span><span class="p">(</span><span class="s2">&#34;subprocess.run&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">def</span> <span class="nf">test_failed_command</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">mock_run</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試失敗的 git 命令&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">mock_result</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">mock_result</span><span class="o">.</span><span class="n">returncode</span> <span class="o">=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="n">mock_result</span><span class="o">.</span><span class="n">stdout</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">mock_result</span><span class="o">.</span><span class="n">stderr</span> <span class="o">=</span> <span class="s2">&#34;fatal: not a git repository&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">mock_run</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="n">mock_result</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertFalse</span><span class="p">(</span><span class="n">success</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertIn</span><span class="p">(</span><span class="s2">&#34;not a git repository&#34;</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="nd">@patch</span><span class="p">(</span><span class="s2">&#34;subprocess.run&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="k">def</span> <span class="nf">test_timeout</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">mock_run</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試命令超時&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">mock_run</span><span class="o">.</span><span class="n">side_effect</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">TimeoutExpired</span><span class="p">(</span><span class="s2">&#34;git&#34;</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">],</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertFalse</span><span class="p">(</span><span class="n">success</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertIn</span><span class="p">(</span><span class="s2">&#34;timed out&#34;</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span></span></span></code></pre></div><h2 id="magicmock">MagicMock</h2>
<p><code>MagicMock</code> 自動支援魔術方法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">MagicMock</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">mock</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 自動支援各種操作</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">mock</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>          <span class="c1"># 不會報錯</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">mock</span><span class="o">.</span><span class="n">anything</span><span class="p">()</span>  <span class="c1"># 返回另一個 MagicMock</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="nb">len</span><span class="p">(</span><span class="n">mock</span><span class="p">)</span>        <span class="c1"># 返回預設值</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="nb">str</span><span class="p">(</span><span class="n">mock</span><span class="p">)</span>        <span class="c1"># 返回字串</span></span></span></code></pre></div><h2 id="測試檔案操作">測試檔案操作</h2>
<h3 id="使用-mock_open">使用 mock_open</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">patch</span><span class="p">,</span> <span class="n">mock_open</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">test_read_config</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">config_content</span> <span class="o">=</span> <span class="s1">&#39;{&#34;key&#34;: &#34;value&#34;}&#39;</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="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;builtins.open&#34;</span><span class="p">,</span> <span class="n">mock_open</span><span class="p">(</span><span class="n">read_data</span><span class="o">=</span><span class="n">config_content</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;config.json&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="s2">&#34;key&#34;</span><span class="p">],</span> <span class="s2">&#34;value&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="測試-path-物件">測試 Path 物件</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">patch</span><span class="p">,</span> <span class="n">Mock</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">test_check_file_exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;pathlib.Path.exists&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_exists</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="n">mock_exists</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">check_file_exists</span><span class="p">(</span><span class="s2">&#34;/some/path&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertTrue</span><span class="p">(</span><span class="n">result</span><span class="p">)</span></span></span></code></pre></div><h2 id="patch-的位置">patch 的位置</h2>
<p><strong>重要</strong>：patch 的目標是模組匯入的位置，而非定義的位置。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># module_a.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">os</span> <span class="kn">import</span> <span class="n">getcwd</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">my_function</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">return</span> <span class="n">getcwd</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># test_module_a.py</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 正確：patch 匯入的位置</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nd">@patch</span><span class="p">(</span><span class="s2">&#34;module_a.getcwd&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">test_my_function</span><span class="p">(</span><span class="n">mock_getcwd</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 錯誤：patch 定義的位置</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nd">@patch</span><span class="p">(</span><span class="s2">&#34;os.getcwd&#34;</span><span class="p">)</span>  <span class="c1"># 不會生效！</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">def</span> <span class="nf">test_my_function</span><span class="p">(</span><span class="n">mock_getcwd</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="o">...</span></span></span></code></pre></div><h2 id="最佳實踐">最佳實踐</h2>
<h3 id="1-只-mock-外部依賴">1. 只 Mock 外部依賴</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 好：Mock 外部系統</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@patch</span><span class="p">(</span><span class="s2">&#34;subprocess.run&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">test_git_command</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">mock_run</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="o">...</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="c1"># 不好：Mock 內部邏輯</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="nd">@patch</span><span class="p">(</span><span class="s2">&#34;my_module.internal_helper&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="k">def</span> <span class="nf">test_my_function</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">mock_helper</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="o">...</span>  <span class="c1"># 過度 mock 會讓測試變脆弱</span></span></span></code></pre></div><h3 id="2-使用-autospec">2. 使用 autospec</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">patch</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># autospec 確保 mock 的簽名與原函式相同</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nd">@patch</span><span class="p">(</span><span class="s2">&#34;module.function&#34;</span><span class="p">,</span> <span class="n">autospec</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">def</span> <span class="nf">test_something</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">mock_func</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="c1"># 如果呼叫簽名錯誤會報錯</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="n">mock_func</span><span class="p">(</span><span class="s2">&#34;wrong&#34;</span><span class="p">,</span> <span class="s2">&#34;args&#34;</span><span class="p">)</span>  <span class="c1"># 可能報錯</span></span></span></code></pre></div><h3 id="3-清理-mock">3. 清理 Mock</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">patcher</span> <span class="o">=</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;module.function&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">mock_func</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">patcher</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">def</span> <span class="nf">tearDown</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">patcher</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>  <span class="c1"># 確保清理</span></span></span></code></pre></div><h2 id="思考題">思考題</h2>
<ol>
<li>patch 的目標為什麼是匯入位置而非定義位置？</li>
<li><code>Mock</code> 和 <code>MagicMock</code> 有什麼區別？</li>
<li>什麼時候應該使用 <code>autospec=True</code>？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>為 <code>get_current_branch()</code> 撰寫使用 Mock 的測試</li>
<li>測試一個讀取檔案的函式，使用 <code>mock_open</code></li>
<li>測試一個會拋出異常的外部呼叫，使用 <code>side_effect</code></li>
</ol>
<hr>
<p><em>上一章：<a href="/blog/python/05-error-testing/unittest/" data-link-title="5.3 unittest 基礎" data-link-desc="撰寫第一個單元測試">unittest 基礎</a></em>
<em>下一模組：<a href="/blog/python/04-oop/" data-link-title="模組四：物件導向設計" data-link-desc="Python 的物件導向設計與設計模式">物件導向設計</a></em></p>
]]></content:encoded></item></channel></rss>