<?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/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>VS Code Extension｜林鼎淵 Dean Lin</title>
    <link>https://deanlin.net/tags/vs-code-extension/</link>
    <image><url>https://deanlin.net/images/author_hu_aa256fc934e9a932.jpg</url><title>VS Code Extension｜林鼎淵 Dean Lin</title><link>https://deanlin.net/tags/vs-code-extension/</link><width>144</width><height>144</height></image>
    <description>林鼎淵（Dean Lin）的部落格：生成式 AI 企業內訓講師、外商資安公司軟體專家、7 本書作者。400 多篇文章用白話分享生成式 AI 實戰、Claude Code 與 Vibe Coding 教學、團隊合作與職場成長。</description>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <copyright>© 2020–2026 林鼎淵 Dean Lin</copyright>
    <lastBuildDate>Thu, 19 Jan 2023 10:55:58 +0800</lastBuildDate>
    <atom:link href="https://deanlin.net/tags/vs-code-extension/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Jest 框架進階應用（describe、Setup and Teardown、Mock function）</title>
      <link>https://deanlin.net/blog/202301/jest-advanced-mock-setup-teardown/</link>
      <pubDate>Thu, 19 Jan 2023 10:55:58 +0800</pubDate>
      <guid>https://deanlin.net/blog/202301/jest-advanced-mock-setup-teardown/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>一個功能底下可能有很多函式，因此我們要對它進行分組（describe）；當我們想測試特定情境時，可以在測試前增加資料（Setup）並於測試後移除（Teardown）；為了讓單元測試更存粹，可以透過模擬函式（Mock function）減少相依性。</description>
      <media:content url="https://deanlin.net/blog/202301/jest-advanced-mock-setup-teardown/images/jest-advanced-mock-setup-teardown-og_hu_8dc9a72b055a1c4f.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p><em>如</em>果你對 Jest 這個測試框架還一無所知，建議先看<a href="https://deanlin.net/blog/202301/jest-unit-testing-typescript/">上一篇文章</a>喔！</p>
</blockquote>
<p>一個功能底下可能有很多函式，如果測試程式沒有適當的區分，那在遇到錯誤時會難以管理，因此我們要對它進行分組（describe）；當我們想測試特定情境時，可以在測試前增加資料（Setup）並於測試後移除（Teardown）；為了讓單元測試更存粹，可以透過模擬函式（Mock function）減少相依性。</p>
<p><img src="https://deanlin.net/blog/202301/jest-advanced-mock-setup-teardown/images/jest-advanced-mock-setup-teardown-01.webp" width="1150" height="597" alt="Jest 框架進階應用（describe、Setup and Teardown、Mock function）（圖 1）" fetchpriority="high" decoding="async"></p>
<pre tabindex="0"><code>大綱

一、測試分組（describe）
二、在測試前、後要執行的任務（Setup and Teardown）
三、用 Mock Function 減少測試時的相依性
四、VSCode 的 Jest 外掛
</code></pre><blockquote>
<p>讀者可以直接到<a href="https://github.com/dean9703111/jest-typescript-starter/tree/master/jest-advanced">筆者的 Github </a>，將專案 Clone 下來跑跑看，會更好理解裏面的邏輯（順手按個 ⭐ 更好 🤩）。</p>
</blockquote>
<h2 id="一測試分組describe">一、測試分組（describe）<a class="h-anchor" href="#%e4%b8%80%e6%b8%ac%e8%a9%a6%e5%88%86%e7%b5%84describe" aria-label="這個段落的連結"></a></h2>
<p>比如今天我們設計一個計算機，裏面包含「加減乘除」等功能，如果要針對不同 function 做分組測試，我們可以這樣做：</p>
<ul>
<li><strong>calculator.ts</strong>：計算機基礎功能（加減乘除）。</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#ff7b72">export</span> <span style="color:#ff7b72">function</span> sum(a: <span style="color:#ff7b72">number</span>, b: <span style="color:#ff7b72">number</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> a <span style="color:#ff7b72;font-weight:bold">+</span> b;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">export</span> <span style="color:#ff7b72">function</span> minus(a: <span style="color:#ff7b72">number</span>, b: <span style="color:#ff7b72">number</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> a <span style="color:#ff7b72;font-weight:bold">-</span> b;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">export</span> <span style="color:#ff7b72">function</span> multiply(a: <span style="color:#ff7b72">number</span>, b: <span style="color:#ff7b72">number</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> a <span style="color:#ff7b72;font-weight:bold">*</span> b;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">export</span> <span style="color:#ff7b72">function</span> divide(a: <span style="color:#ff7b72">number</span>, b: <span style="color:#ff7b72">number</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> a <span style="color:#ff7b72;font-weight:bold">/</span> b;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><ul>
<li><strong>calculator.test.ts</strong>：分組測試計算機基礎功能。</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> { sum, minus, multiply, divide } <span style="color:#ff7b72">from</span> <span style="color:#a5d6ff">&#34;./calculator&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>describe(<span style="color:#a5d6ff">&#34;測試加法&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>  test(<span style="color:#a5d6ff">&#34;1 + 1 = 2&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    expect(sum(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">1</span>)).toBe(<span style="color:#a5d6ff">2</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>  test(<span style="color:#a5d6ff">&#34;3 + 3 = 7&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    expect(sum(<span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">4</span>)).toBe(<span style="color:#a5d6ff">7</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>describe(<span style="color:#a5d6ff">&#34;測試減法&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>  test(<span style="color:#a5d6ff">&#34;5 - 1 = 4&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    expect(minus(<span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">1</span>)).toBe(<span style="color:#a5d6ff">4</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>  test(<span style="color:#a5d6ff">&#34;3 - 3 = 0&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    expect(minus(<span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">3</span>)).toBe(<span style="color:#a5d6ff">0</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>describe(<span style="color:#a5d6ff">&#34;測試乘法&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>  test(<span style="color:#a5d6ff">&#34;1 * 5 = 5&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    expect(multiply(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">5</span>)).toBe(<span style="color:#a5d6ff">5</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>  test(<span style="color:#a5d6ff">&#34;3 * 3 = 9&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    expect(multiply(<span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">3</span>)).toBe(<span style="color:#a5d6ff">9</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>describe(<span style="color:#a5d6ff">&#34;測試除法&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>  test(<span style="color:#a5d6ff">&#34;6 / 2 = 3&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    expect(divide(<span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">2</span>)).toBe(<span style="color:#a5d6ff">3</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>  test(<span style="color:#a5d6ff">&#34;3 / 2 = 1.5&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    expect(divide(<span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">2</span>)).toBe(<span style="color:#a5d6ff">1.5</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>運行測試指令 <code>npm run test</code>，順利的話就會看到下圖測試分組的結果嚕！</p>
<p><img src="https://deanlin.net/blog/202301/jest-advanced-mock-setup-teardown/images/jest-advanced-mock-setup-teardown-02.webp" width="946" height="1042" alt="一、測試分組（describe）（圖 2）" loading="lazy" decoding="async"></p>
<hr>
<h2 id="二在測試前後要執行的任務setup-and-teardown">二、在測試前、後要執行的任務（Setup and Teardown）<a class="h-anchor" href="#%e4%ba%8c%e5%9c%a8%e6%b8%ac%e8%a9%a6%e5%89%8d%e5%be%8c%e8%a6%81%e5%9f%b7%e8%a1%8c%e7%9a%84%e4%bb%bb%e5%8b%99setup-and-teardown" aria-label="這個段落的連結"></a></h2>
<p>Jest 有提供 4 種函數：</p>
<ul>
<li><strong>beforeAll</strong>：在所有測試開始前執行。</li>
<li><strong>afterAll</strong>：在所有測試完畢後執行。</li>
<li><strong>beforeEach</strong>：在每個測試開始前執行。</li>
<li><strong>afterEach</strong>：在每個測試結束後執行。</li>
</ul>
<p>舉個例子，如果今天要設計「考核系統」，除了有考生外，還需要模擬出「合格」、「不合格」的情境，為了避免影響到資料庫，我們可以這樣做：</p>
<ul>
<li><strong>user.ts</strong>：我們先預設的 score 的基準為 60 分，可透過 addScore、minusScore 來加減分。</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#ff7b72">export</span> <span style="color:#ff7b72">default</span> <span style="color:#ff7b72">class</span> user {
</span></span><span style="display:flex;"><span>  name: <span style="color:#ff7b72">string</span>;
</span></span><span style="display:flex;"><span>  score: <span style="color:#ff7b72">number</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">constructor</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">this</span>.name <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;&#34;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">this</span>.score <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">60</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  addScore() {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">this</span>.score <span style="color:#ff7b72;font-weight:bold">+=</span> <span style="color:#a5d6ff">10</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  minusScore() {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">this</span>.score <span style="color:#ff7b72;font-weight:bold">-=</span> <span style="color:#a5d6ff">10</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><ul>
<li><strong>exam.test.ts</strong>：利用「beforeAll」將考生的資料初始化，並透過「beforeEach」將分組測試的分數重新校正（若少了這個動作，第二個測試會沿用上一個測試的結果，會變成 70–10=60 分），測試完畢後用「afterAll」將考生資料清空。</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> user <span style="color:#ff7b72">from</span> <span style="color:#a5d6ff">&#34;./user&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> userDB: <span style="color:#ff7b72">user</span>[] <span style="color:#ff7b72;font-weight:bold">=</span> [];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> insertTestData <span style="color:#ff7b72;font-weight:bold">=</span> (name: <span style="color:#ff7b72">string</span>) <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">const</span> User <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> user();
</span></span><span style="display:flex;"><span>  User.name <span style="color:#ff7b72;font-weight:bold">=</span> name;
</span></span><span style="display:flex;"><span>  userDB.push(User);
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> clearTestData <span style="color:#ff7b72;font-weight:bold">=</span> () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>  userDB.length <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>;
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>beforeAll(() <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>  insertTestData(<span style="color:#a5d6ff">&#34;baobao&#34;</span>);
</span></span><span style="display:flex;"><span>  insertTestData(<span style="color:#a5d6ff">&#34;mochi&#34;</span>);
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>describe(<span style="color:#a5d6ff">&#34;baoboa 分數計算&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>  beforeEach(() <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    userDB[<span style="color:#a5d6ff">0</span>].score <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">60</span>;
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>  test(<span style="color:#a5d6ff">&#34;60 + 10 為 70 分&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    userDB[<span style="color:#a5d6ff">0</span>].addScore();
</span></span><span style="display:flex;"><span>    expect(userDB[<span style="color:#a5d6ff">0</span>].score).toBe(<span style="color:#a5d6ff">70</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>  test(<span style="color:#a5d6ff">&#34;60 - 10 為 50 分&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    userDB[<span style="color:#a5d6ff">0</span>].minusScore();
</span></span><span style="display:flex;"><span>    expect(userDB[<span style="color:#a5d6ff">0</span>].score).toBe(<span style="color:#a5d6ff">50</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>describe(<span style="color:#a5d6ff">&#34;mochi 分數計算&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>  beforeEach(() <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    userDB[<span style="color:#a5d6ff">1</span>].score <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">60</span>;
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>  test(<span style="color:#a5d6ff">&#34;60 + 10 為 70 分&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    userDB[<span style="color:#a5d6ff">1</span>].addScore();
</span></span><span style="display:flex;"><span>    expect(userDB[<span style="color:#a5d6ff">1</span>].score).toBe(<span style="color:#a5d6ff">70</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>  test(<span style="color:#a5d6ff">&#34;60 - 10 為 50 分&#34;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    userDB[<span style="color:#a5d6ff">1</span>].minusScore();
</span></span><span style="display:flex;"><span>    expect(userDB[<span style="color:#a5d6ff">1</span>].score).toBe(<span style="color:#a5d6ff">50</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>afterAll(() <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>  clearTestData();
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>這邊補充說明一下，運行單個測試檔案的指令為： <code>npm run test -t path/user.test.ts</code>。</p>
<p>如果運行順利，就會看到下圖的測試結果嚕！</p>
<p><img src="https://deanlin.net/blog/202301/jest-advanced-mock-setup-teardown/images/jest-advanced-mock-setup-teardown-03.webp" width="1232" height="624" alt="二、在測試前、後要執行的任務（Setup and Teardown）（圖 3）" loading="lazy" decoding="async"></p>
<hr>
<h2 id="三用-mock-function-減少測試時的相依性">三、用 Mock Function 減少測試時的相依性<a class="h-anchor" href="#%e4%b8%89%e7%94%a8-mock-function-%e6%b8%9b%e5%b0%91%e6%b8%ac%e8%a9%a6%e6%99%82%e7%9a%84%e7%9b%b8%e4%be%9d%e6%80%a7" aria-label="這個段落的連結"></a></h2>
<p>單元測試時，我們更關心「單一功能」的正確與否；如果一個單元測試涵蓋太多函式，那在發生錯誤時，我們很難判斷到底是哪一段發生錯誤。</p>
<ul>
<li><strong>fetch.js</strong>：我們透過「fetchData」呼叫外部 api，而「hideComplete」是設計用來隱藏已完成的 todos。</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> axios <span style="color:#ff7b72">from</span> <span style="color:#a5d6ff">&#34;axios&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">export</span> <span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> fetchData() {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">const</span> response <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> axios.<span style="color:#ff7b72">get</span>(<span style="color:#a5d6ff">`https://jsonplaceholder.typicode.com/todos`</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> response;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">export</span> <span style="color:#ff7b72">function</span> hideComplete(data) {
</span></span><span style="display:flex;"><span>  data <span style="color:#ff7b72;font-weight:bold">=</span> data.filter(<span style="color:#ff7b72">function</span> (value<span style="color:#ff7b72;font-weight:bold">:</span> { completed: <span style="color:#ff7b72">boolean</span> }) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> value.completed <span style="color:#ff7b72;font-weight:bold">===</span> <span style="color:#79c0ff">false</span>;
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> data;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><ul>
<li><strong>fetch.test.js</strong>：為了減少相依性，我們不希望直接呼叫真實的 api，所以透過 <code>jest.mock</code> 將 axios 給 mock 起來；並透過 3 種情境測試：「有成功換成模擬資料、資料格式符合預期、獨立測試 hideComplete 函式」。</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> { fetchData, hideComplete } from <span style="color:#a5d6ff">&#34;./fetch&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> axios from <span style="color:#a5d6ff">&#34;axios&#34;</span>;
</span></span><span style="display:flex;"><span>jest.mock(<span style="color:#a5d6ff">&#34;axios&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> mockedAxios <span style="color:#ff7b72;font-weight:bold">=</span> axios as jest.Mocked<span style="color:#ff7b72;font-weight:bold">&lt;</span><span style="color:#ff7b72">typeof</span> axios<span style="color:#ff7b72;font-weight:bold">&gt;</span>;
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// 模擬我們期望收到的資料結構
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> data <span style="color:#ff7b72;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>    userId<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">1</span>,
</span></span><span style="display:flex;"><span>    id<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">1</span>,
</span></span><span style="display:flex;"><span>    title<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;task 1&#34;</span>,
</span></span><span style="display:flex;"><span>    completed<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#79c0ff">false</span>,
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>    userId<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">2</span>,
</span></span><span style="display:flex;"><span>    id<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">2</span>,
</span></span><span style="display:flex;"><span>    title<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;task 2&#34;</span>,
</span></span><span style="display:flex;"><span>    completed<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#79c0ff">true</span>,
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>test(<span style="color:#a5d6ff">&#34;確認結果被替換成模擬資料&#34;</span>, <span style="color:#ff7b72">async</span> () =&gt; {
</span></span><span style="display:flex;"><span>  mockedAxios.get.mockResolvedValue(data);
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">const</span> result <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> fetchData();
</span></span><span style="display:flex;"><span>  expect(result).toEqual(data);
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>test(<span style="color:#a5d6ff">&#34;驗證資料格式&#34;</span>, <span style="color:#ff7b72">async</span> () =&gt; {
</span></span><span style="display:flex;"><span>  mockedAxios.get.mockResolvedValue(data);
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">const</span> result <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> fetchData();
</span></span><span style="display:flex;"><span>  expect(<span style="color:#ff7b72">typeof</span> result[<span style="color:#a5d6ff">0</span>].userId).toBe(<span style="color:#a5d6ff">&#34;number&#34;</span>);
</span></span><span style="display:flex;"><span>  expect(<span style="color:#ff7b72">typeof</span> result[<span style="color:#a5d6ff">0</span>].id).toBe(<span style="color:#a5d6ff">&#34;number&#34;</span>);
</span></span><span style="display:flex;"><span>  expect(<span style="color:#ff7b72">typeof</span> result[<span style="color:#a5d6ff">0</span>].title).toBe(<span style="color:#a5d6ff">&#34;string&#34;</span>);
</span></span><span style="display:flex;"><span>  expect(<span style="color:#ff7b72">typeof</span> result[<span style="color:#a5d6ff">0</span>].completed).toBe(<span style="color:#a5d6ff">&#34;boolean&#34;</span>);
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>test(<span style="color:#a5d6ff">&#34;測試 hideComplete 函式運作&#34;</span>, <span style="color:#ff7b72">async</span> () =&gt; {
</span></span><span style="display:flex;"><span>  mockedAxios.get.mockResolvedValue(data);
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> result <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> fetchData();
</span></span><span style="display:flex;"><span>  result <span style="color:#ff7b72;font-weight:bold">=</span> hideComplete(result);
</span></span><span style="display:flex;"><span>  expect(result[<span style="color:#a5d6ff">0</span>].completed).toBe(<span style="color:#79c0ff">false</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>輸入<code>npm run test -t path/user.test.ts</code>後，如果順利就會看到下圖的測試結果嚕！</p>
<p><img src="https://deanlin.net/blog/202301/jest-advanced-mock-setup-teardown/images/jest-advanced-mock-setup-teardown-04.webp" width="1256" height="616" alt="三、用 Mock Function 減少測試時的相依性（圖 4）" loading="lazy" decoding="async"></p>
<hr>
<h2 id="四vscode-的-jest-外掛">四、VSCode 的 Jest 外掛<a class="h-anchor" href="#%e5%9b%9bvscode-%e7%9a%84-jest-%e5%a4%96%e6%8e%9b" aria-label="這個段落的連結"></a></h2>
<p>每新增一個測試就要跑一次 <code>npm run test</code> 是不是很煩？如果想獨立測試某個檔案還要先確認文件路徑真的讓人心累 😨</p>
<p>但好在 VSCode 有「<a href="https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest">Jest</a>」這個 Extensions 讓我們事半功倍。</p>
<p><img src="https://deanlin.net/blog/202301/jest-advanced-mock-setup-teardown/images/jest-advanced-mock-setup-teardown-05.webp" width="1400" height="480" alt="四、VSCode 的 Jest 外掛（圖 5）" loading="lazy" decoding="async"></p>
<p>安裝完成後，VSCode 的左側會多出一個「Testing」的 icon，只要你在專案下有設定 <code>jest.config.js</code> 就會出現（如果還未設定可以參考<a href="https://deanlin.net/blog/202301/jest-unit-testing-typescript/">上一篇文章</a>）。</p>
<p><img src="https://deanlin.net/blog/202301/jest-advanced-mock-setup-teardown/images/jest-advanced-mock-setup-teardown-06.webp" width="1400" height="615" alt="四、VSCode 的 Jest 外掛（圖 6）" loading="lazy" decoding="async"></p>
<p>在 test 文檔左側即可看到測試成功的「v」與失敗的「x」。</p>
<p><img src="https://deanlin.net/blog/202301/jest-advanced-mock-setup-teardown/images/jest-advanced-mock-setup-teardown-07.webp" width="1048" height="462" alt="四、VSCode 的 Jest 外掛（圖 7）" loading="lazy" decoding="async"></p>
<p>失敗的測試會在 expect 下方出現紅色底線，滑鼠移過去就會顯示預期值（Expected）與接收值（Received）的差異。</p>
<p><img src="https://deanlin.net/blog/202301/jest-advanced-mock-setup-teardown/images/jest-advanced-mock-setup-teardown-08.webp" width="1400" height="641" alt="四、VSCode 的 Jest 外掛（圖 8）" loading="lazy" decoding="async"></p>
<blockquote>
<p><strong>備註</strong>：身邊有朋友在安裝外掛後遇到了一些奇怪的 Error 無法正常使用，重開 VSCode、看官方文檔怎麼調整都無用，最後用大絕「重新開機」就正常了</p>
</blockquote>
<hr>
<p>常用的 Jest 應用就介紹到這邊，真的要自己動手實驗過才會理解裏面的原理喔！</p>
<a class="linkcard" href="https://deanlin.net/series/frontend-testing/">
  <span class="lc-body">
    <span class="lc-title">前端單元測試</span>
    <span class="lc-desc">替前端專案導入測試：學習地圖、Jest 基礎與進階，再到 Vuetify（Vite &amp; TypeScript）中的 @vue/test-utils。</span>
    <span class="lc-site"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M5 12h14M13 6l6 6-6 6"/></svg>林鼎淵 Dean Lin</span>
  </span>
</a>

]]></content:encoded>
    </item>
    <item>
      <title>使用 Visual Studio Live Share 來即時共同開發 — Pair programming</title>
      <link>https://deanlin.net/blog/202106/vscode-live-share-pair-programming/</link>
      <pubDate>Mon, 21 Jun 2021 17:52:13 +0800</pubDate>
      <guid>https://deanlin.net/blog/202106/vscode-live-share-pair-programming/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>團隊合作</category>
      <description>在遠端工作後，我們使用 Teams、Skype、Google meet…</description>
      <media:content url="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-og_hu_bc7be76d4a153ac0.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>在遠端工作後，我們使用 Teams、Skype、Google meet 來完成日常的會議，但如果使用上述工具來討論專案中的程式碼並不方便，因為所有使用者都聚焦在同一個畫面上；但看到問題時只能用口頭表達：「去app資料架下面找出xxxController，第30行的程式邏輯有問題。」這樣的表達方式欠缺效率且容易帶有情緒；所以今天向大家介紹一款共同開發超棒的工具。</p>
<figure class="figure"><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-01.webp" width="1400" height="785" alt="圖片來源：Visual Studio官網" fetchpriority="high" decoding="async">
  <figcaption>圖片來源：<a href="https://visualstudio.microsoft.com/zh-hant/services/live-share/?fbclid=IwAR3B2oFFd4kuofFKVm5qh7r5T-QCdOXSSta87b87jBhGGTdob4Ztl3Gp6-U">Visual Studio官網</a></figcaption>
</figure>

<pre tabindex="0"><code>一、我們現在協作時遇到了哪些困境？
二、這個工具能給我們開發帶來什麼樣的幫助？
三、安裝套件＆登入使用者
四、使用方式
</code></pre><h2 id="一我們現在協作時遇到了哪些困境">一、我們現在協作時遇到了哪些困境？<a class="h-anchor" href="#%e4%b8%80%e6%88%91%e5%80%91%e7%8f%be%e5%9c%a8%e5%8d%94%e4%bd%9c%e6%99%82%e9%81%87%e5%88%b0%e4%ba%86%e5%93%aa%e4%ba%9b%e5%9b%b0%e5%a2%83" aria-label="這個段落的連結"></a></h2>
<ul>
<li>使用螢幕分享做 Debug 時，需要依靠大腦來做運算邏輯，想要看哪一支檔案與程式也要靠口述，非常不方便</li>
<li>有些程式（Teams），不只螢幕分享來能直接控制對方的電腦做操作，但同一時間只允許一人做操作</li>
<li>就算團隊使用 git 版控，你切到對方有 Bug 的 branch 時可能無法順利重現問題發生（很多套件版本及環境參數設定不同）</li>
<li>因為無法完全模擬對方程式運作環境導致 Debug 時困難重重</li>
</ul>
<h2 id="二這個工具能給我們開發帶來什麼樣的幫助">二、這個工具能給我們開發帶來什麼樣的幫助？<a class="h-anchor" href="#%e4%ba%8c%e9%80%99%e5%80%8b%e5%b7%a5%e5%85%b7%e8%83%bd%e7%b5%a6%e6%88%91%e5%80%91%e9%96%8b%e7%99%bc%e5%b8%b6%e4%be%86%e4%bb%80%e9%ba%bc%e6%a8%a3%e7%9a%84%e5%b9%ab%e5%8a%a9" aria-label="這個段落的連結"></a></h2>
<ul>
<li>對方能夠很清楚理解自己現在講述的程式在哪個檔案的哪個位置，不再雞同鴨講</li>
<li>雙方能自由瀏覽專案下的資料夾與程式，無需依靠口述</li>
<li>可以模擬對方的環境進行程式編譯，更清楚問題發生點</li>
<li>雙方的游標顏色不同，方便討論時不會產生誤會</li>
</ul>
<h2 id="三安裝套件登入使用者">三、安裝套件＆登入使用者<a class="h-anchor" href="#%e4%b8%89%e5%ae%89%e8%a3%9d%e5%a5%97%e4%bb%b6%e7%99%bb%e5%85%a5%e4%bd%bf%e7%94%a8%e8%80%85" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>下面的步驟及操作可能會隨著 VScode 改版而有所不同，僅供參考</p>
</blockquote>
<p><strong>SETP 1</strong>：在 VScode 的 EXTENSION 分頁搜尋「Live Share」，點擊「install」即可完成安裝</p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-02.webp" width="1400" height="658" alt="三、安裝套件＆登入使用者（圖 2）" loading="lazy" decoding="async"></p>
<p><strong>SETP 2</strong>：安裝完後會在左下角多出「LIVE SHARE」的icon</p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-03.webp" width="514" height="664" alt="三、安裝套件＆登入使用者（圖 3）" loading="lazy" decoding="async"></p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-04.webp" width="1296" height="862" alt="三、安裝套件＆登入使用者（圖 4）" loading="lazy" decoding="async"></p>
<p><strong>SETP 3</strong>：第一次按「Share」時，會要求你登入 GitHub 或是 Microsoft 的帳號</p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-05.webp" width="1296" height="862" alt="三、安裝套件＆登入使用者（圖 5）" loading="lazy" decoding="async"></p>
<p><strong>SETP 4</strong>：這裡我選擇登入Github，基本上就是一路按接受授權的過程</p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-06.webp" width="1400" height="573" alt="三、安裝套件＆登入使用者（圖 6）" loading="lazy" decoding="async"></p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-07.webp" width="1400" height="1429" alt="三、安裝套件＆登入使用者（圖 7）" loading="lazy" decoding="async"></p>
<p><strong>SETP 5</strong>：註冊成功後，你就會看到以下畫面</p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-08.webp" width="1300" height="668" alt="三、安裝套件＆登入使用者（圖 8）" loading="lazy" decoding="async"></p>
<h2 id="四使用方式">四、使用方式<a class="h-anchor" href="#%e5%9b%9b%e4%bd%bf%e7%94%a8%e6%96%b9%e5%bc%8f" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>完成安裝＆登入的步驟後，下面提供本機模擬的操作方式</p>
</blockquote>
<h3 id="-自己跟自己的連線模擬操作">自己跟自己的連線，模擬操作<a class="h-anchor" href="#-%e8%87%aa%e5%b7%b1%e8%b7%9f%e8%87%aa%e5%b7%b1%e7%9a%84%e9%80%a3%e7%b7%9a%e6%a8%a1%e6%93%ac%e6%93%8d%e4%bd%9c" aria-label="這個段落的連結"></a></h3>
<p><strong>SETP 1</strong>：在本機用 VScode 開啟一個資料夾或是專案</p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-09.webp" width="1286" height="856" alt="自己跟自己的連線，模擬操作（圖 9）" loading="lazy" decoding="async"></p>
<p><strong>SETP 2</strong>：點擊「Share」做分享，此時你會得到一組邀請連結</p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-10.webp" width="945" height="856" alt="自己跟自己的連線，模擬操作（圖 10）" loading="lazy" decoding="async"></p>
<p><strong>SETP 3</strong>：此時開用「New Windows」開啟一個空的 VScode，點擊「Join」加入</p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-11.webp" width="1020" height="761" alt="自己跟自己的連線，模擬操作（圖 11）" loading="lazy" decoding="async"></p>
<p><strong>SETP 4</strong>：加入後加入後便可以開始共同開發嚕</p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-12.webp" width="1174" height="855" alt="自己跟自己的連線，模擬操作（圖 12）" loading="lazy" decoding="async"></p>
<p>當 Join 的人對檔案操作時，Share 的人可以看到他的操作</p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-13.webp" width="1400" height="681" alt="自己跟自己的連線，模擬操作（圖 13）" loading="lazy" decoding="async"></p>
<h3 id="-了解-share-的人跟-join-的人正在操作的畫面">了解 Share 的人跟 Join 的人正在操作的畫面<a class="h-anchor" href="#-%e4%ba%86%e8%a7%a3-share-%e7%9a%84%e4%ba%ba%e8%b7%9f-join-%e7%9a%84%e4%ba%ba%e6%ad%a3%e5%9c%a8%e6%93%8d%e4%bd%9c%e7%9a%84%e7%95%ab%e9%9d%a2" aria-label="這個段落的連結"></a></h3>
<p>按下右上角的「圖釘」，就是「Follow Participant」觀看對方現在操作的檔案</p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-14.webp" width="915" height="395" alt="了解 Share 的人跟 Join 的人正在操作的畫面（圖 14）" loading="lazy" decoding="async"></p>
<h3 id="-直接在對方的環境下執行程式了解問題">直接在對方的環境下執行程式了解問題<a class="h-anchor" href="#-%e7%9b%b4%e6%8e%a5%e5%9c%a8%e5%b0%8d%e6%96%b9%e7%9a%84%e7%92%b0%e5%a2%83%e4%b8%8b%e5%9f%b7%e8%a1%8c%e7%a8%8b%e5%bc%8f%e4%ba%86%e8%a7%a3%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<p>Join的人可以直接在終端機下指令，了解程式執行狀況</p>
<p><img src="https://deanlin.net/blog/202106/vscode-live-share-pair-programming/images/vscode-live-share-pair-programming-15.webp" width="1354" height="826" alt="直接在對方的環境下執行程式了解問題（圖 15）" loading="lazy" decoding="async"></p>
<hr>
<blockquote class="pullquote"><p>本篇文章只是很簡略地讓大家了解如何使用「Live Share」這款套件，更多的功能就讓大家<a href="https://visualstudio.microsoft.com/zh-hant/services/live-share/?fbclid=IwAR3B2oFFd4kuofFKVm5qh7r5T-QCdOXSSta87b87jBhGGTdob4Ztl3Gp6-U">直接去官網</a>了解吧！</p>
</blockquote>
]]></content:encoded>
    </item>
  </channel>
</rss>
