<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>TDD on kenji.blog</title><link>http://kenji.blog/zh-tw/tags/tdd/</link><description>Recent content in TDD on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>zh-tw</language><copyright>kenjinote</copyright><lastBuildDate>Sat, 12 Sep 2026 21:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/zh-tw/tags/tdd/index.xml" rel="self" type="application/rss+xml"/><item><title>如何在 C++ 專案（GoogleTest 等）中導入測試驅動開發（TDD）</title><link>http://kenji.blog/zh-tw/p/tdd-cpp-googletest-introduction/</link><pubDate>Sat, 12 Sep 2026 21:00:00 +0900</pubDate><guid>http://kenji.blog/zh-tw/p/tdd-cpp-googletest-introduction/</guid><description>&lt;img src="http://kenji.blog/p/tdd-cpp-googletest-introduction/img/eyecatch.jpg" alt="Featured image of post 如何在 C++ 專案（GoogleTest 等）中導入測試驅動開發（TDD）" />&lt;p>在現代的軟體開發中，在維持程式碼品質的同時迅速添加新功能，是至關重要的任務。特別是在 C++ 這種要求效能且複雜的語言中，記憶體管理的失誤或未定義行為（Undefined Behavior）很容易導致致命的錯誤（Bug），因此測試的重要性比其他語言來得更高。&lt;/p>
&lt;p>本文將針對在 C++ 專案中導入**測試驅動開發（Test-Driven Development: TDD）**的方法，進行非常詳細且具實踐性的解說。內容將全面涵蓋單元測試框架 &lt;strong>GoogleTest&lt;/strong> 及 Mock 框架 &lt;strong>GoogleMock&lt;/strong> 的使用方法，還有如何使用建置系統 &lt;strong>CMake&lt;/strong> 進行現代化的配置，以及程式碼覆蓋率（Code Coverage）的測量方法。&lt;/p>
&lt;h2 id="1-測試驅動開發tdd的哲學與優勢">1. 測試驅動開發（TDD）的哲學與優勢
&lt;/h2>&lt;p>測試驅動開發（TDD）是一種「在撰寫實作前先寫測試」的軟體開發方法。這不僅僅是一種測試方法，它也發揮了&lt;strong>設計方法&lt;/strong>的作用。透過先寫測試，開發者會自然而然地意識到「易於使用的介面」與「低耦合的設計」。&lt;/p>
&lt;h3 id="11-red-green-refactor-循環">1.1 Red-Green-Refactor 循環
&lt;/h3>&lt;p>TDD 的核心是以下被稱為「Red-Green-Refactor」的循環。&lt;/p>
&lt;pre class="mermaid">
flowchart TD
Start[&amp;#34;開始開發&amp;#34;] --&amp;gt; Red[&amp;#34;Red: 撰寫會失敗的測試&amp;#34;]
Red --&amp;gt; Green[&amp;#34;Green: 進行能通過測試的最小限度實作&amp;#34;]
Green --&amp;gt; Refactor[&amp;#34;Refactor: 改善程式碼（重構）&amp;#34;]
Refactor --&amp;gt; Red
Refactor -.-&amp;gt; End[&amp;#34;功能完成&amp;#34;]
&lt;/pre>
&lt;ol>
&lt;li>&lt;strong>Red（紅）&lt;/strong>: 在沒有實作的狀態下，撰寫定義期望行為的測試。因為此時尚未實作，測試必定會失敗（Red）。&lt;/li>
&lt;li>&lt;strong>Green（綠）&lt;/strong>: 撰寫僅為了讓測試成功（Green）的最小限度程式碼。在這個階段，程式碼的美觀或效能不是最優先的。&lt;/li>
&lt;li>&lt;strong>Refactor（重構）&lt;/strong>: 在維持測試通過的狀態下，消除重複，改善程式碼的設計。有了測試，就能安全地修改程式碼。&lt;/li>
&lt;/ol>
&lt;h3 id="12-錯誤發現延遲所增加的成本">1.2 錯誤發現延遲所增加的成本
&lt;/h3>&lt;p>在軟體工程中，眾所周知，越是在開發流程的後期才發現錯誤，其修復成本就會呈現指數級增長。這種成本增加的模型，有時會用以下公式來近似。&lt;/p>
$$ Cost(t) = C_0 \times e^{k \cdot t} $$&lt;p>這裡的 $Cost(t)$ 是時間 $t$ 時的修復成本，$C_0$ 是錯誤剛被植入時的修復成本（基準線），$k$ 是常數。透過導入 TDD，可以將 $t$ 保持在極小值，防範成本呈指數級增長。&lt;/p>
&lt;h2 id="2-c-測試工具的選擇與現代化-cmake-配置">2. C++ 測試工具的選擇與現代化 CMake 配置
&lt;/h2>&lt;p>C++ 中存在著許多的測試框架，例如 Catch2、Boost.Test、doctest 等，但作為業界標準最廣泛被使用的是 &lt;strong>GoogleTest（gtest）&lt;/strong>。GoogleTest 的魅力在於其豐富的斷言（Assertion）、強大的 Mock 框架（GoogleMock）以及高度的擴充性。&lt;/p>
&lt;h3 id="21-利用-cmake-的-fetchcontent-導入-googletest">2.1 利用 CMake 的 &lt;code>FetchContent&lt;/code> 導入 GoogleTest
&lt;/h3>&lt;p>在現代的 C++ 開發中，管理外部依賴關係的主流方式是使用 CMake 的 &lt;code>FetchContent&lt;/code> 模組。這可以省去管理子模組（Submodule）或事先安裝函式庫的麻煩。&lt;/p>
&lt;p>專案根目錄的 &lt;code>CMakeLists.txt&lt;/code> 撰寫如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.14&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">TddCppExample&lt;/span> &lt;span class="s">CXX&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 指定 C++ 標準
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CMAKE_CXX_STANDARD&lt;/span> &lt;span class="s">17&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CMAKE_CXX_STANDARD_REQUIRED&lt;/span> &lt;span class="s">ON&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 產品程式碼函式庫化
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">add_library&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">core_lib&lt;/span> &lt;span class="s">src/Calculator.cpp&lt;/span> &lt;span class="s">src/StringUtils.cpp&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">target_include_directories&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">core_lib&lt;/span> &lt;span class="s">PUBLIC&lt;/span> &lt;span class="s">include&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 啟用測試
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">enable_testing&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 取得 GoogleTest
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">FetchContent&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">FetchContent_Declare&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">googletest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">URL&lt;/span> &lt;span class="s">https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 為避免 Windows 環境下的建置警告
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">gtest_force_shared_crt&lt;/span> &lt;span class="s">ON&lt;/span> &lt;span class="s">CACHE&lt;/span> &lt;span class="s">BOOL&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span> &lt;span class="s">FORCE&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">FetchContent_MakeAvailable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">googletest&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 設定測試執行檔
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">add_executable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">unit_tests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">tests/CalculatorTest.cpp&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">tests/StringUtilsTest.cpp&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">target_link_libraries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">unit_tests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PRIVATE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">core_lib&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">gtest_main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">gmock&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 註冊至 CTest
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">GoogleTest&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">gtest_discover_tests&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">unit_tests&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>透過此設定，CMake 會自動下載 GoogleTest 的原始碼並整合進專案中。&lt;/p>
&lt;h2 id="3-實踐googletest-的-red-green-refactor-循環">3. 實踐：GoogleTest 的 Red-Green-Refactor 循環
&lt;/h2>&lt;p>接下來，我們以簡單的 &lt;code>Calculator&lt;/code> 類別為題材，實際演練 TDD 的循環。&lt;/p>
&lt;h3 id="31-階段-1-red-撰寫會失敗的測試">3.1 階段 1: Red (撰寫會失敗的測試)
&lt;/h3>&lt;p>首先，撰寫標頭檔 &lt;code>include/Calculator.h&lt;/code> 的骨架（Skeleton），以及測試程式碼。&lt;/p>
&lt;p>&lt;strong>include/Calculator.h (骨架)&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#pragma once
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Calculator&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>tests/CalculatorTest.cpp (測試程式碼)&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;gtest/gtest.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;Calculator.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">TEST&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CalculatorTest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">AddsTwoPositiveNumbers&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="cl"> &lt;span class="n">Calculator&lt;/span> &lt;span class="n">calc&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">calc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&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="cl"> &lt;span class="n">EXPECT_EQ&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">5&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在這個階段嘗試建置的話，會因為沒有 &lt;code>Calculator::Add&lt;/code> 的實作而發生連結錯誤，或是執行測試後處於失敗的狀態（Red）。&lt;/p>
&lt;h3 id="32-階段-2-green-最小限度的實作">3.2 階段 2: Green (最小限度的實作)
&lt;/h3>&lt;p>撰寫僅為了讓測試通過的程式碼。&lt;/p>
&lt;p>&lt;strong>src/Calculator.cpp&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;Calculator.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="n">Calculator&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">b&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="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">b&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="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>建置並執行測試後，測試將會成功（Green）。&lt;/p>
&lt;h3 id="33-階段-3-refactor-重構">3.3 階段 3: Refactor (重構)
&lt;/h3>&lt;p>雖然在這個例子中程式碼非常簡單，但隨著需求變複雜，在重構階段會提升程式碼的易讀性，或是改善效能。測試程式碼本身也是重構的對象。例如，可以考慮導入測試治具（Test Fixture，&lt;code>testing::Test&lt;/code>）來將設定（Setup）共用化。&lt;/p>
&lt;h2 id="4-expect_eq-與-assert_eq-的差異">4. &lt;code>EXPECT_EQ&lt;/code> 與 &lt;code>ASSERT_EQ&lt;/code> 的差異
&lt;/h2>&lt;p>在使用 GoogleTest 時，有 &lt;code>EXPECT_*&lt;/code> 與 &lt;code>ASSERT_*&lt;/code> 兩種斷言巨集（Macro）。理解它們的差異對於撰寫穩健的測試來說非常重要。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>EXPECT_EQ(expected, actual)&lt;/code>&lt;/strong>: 即使測試失敗，也會&lt;strong>繼續&lt;/strong>執行當前的測試函式。適合在單個測試中想要驗證多個狀態的情況。&lt;/li>
&lt;li>&lt;strong>&lt;code>ASSERT_EQ(expected, actual)&lt;/code>&lt;/strong>: 當測試失敗時，會當場**中斷（致命失敗）**當前測試函式的執行。使用在後續的驗證已經沒有意義的情況（例：確認指標不是 &lt;code>nullptr&lt;/code> 之後立刻進行反參照（Dereference））。&lt;/li>
&lt;/ul>
&lt;h2 id="5-依賴注入di與-googlemock-的-mock-化">5. 依賴注入（DI）與 GoogleMock 的 Mock 化
&lt;/h2>&lt;p>在實際的 C++ 專案中，必定會發生對外部系統的依賴，如資料庫存取、網路通訊、硬體控制等。如果將這些依賴關係放著不管，單元測試會變得非常困難。&lt;/p>
&lt;p>這時就要用到&lt;strong>依賴注入（Dependency Injection: DI）&lt;/strong>，以及使用 &lt;strong>GoogleMock&lt;/strong> 進行介面的 Mock 化。&lt;/p>
&lt;pre class="mermaid">
flowchart LR
Test[&amp;#34;單元測試&amp;#34;] --&amp;gt;|注入 Mock| Target[&amp;#34;目標服務&amp;#34;]
Target --&amp;gt;|依賴於| Interface[&amp;#34;介面 (IUserRepository)&amp;#34;]
Mock[&amp;#34;MockUserRepository&amp;#34;] -.-&amp;gt;|實作| Interface
Test --&amp;gt;|配置| Mock
&lt;/pre>
&lt;h3 id="51-介面的定義與目標類別的實作">5.1 介面的定義與目標類別的實作
&lt;/h3>&lt;p>首先，定義將依賴的元件抽象化後的介面（帶有純虛擬函式的類別）。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// include/IUserRepository.h
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cp">#pragma once
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;string&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">IUserRepository&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">virtual&lt;/span> &lt;span class="o">~&lt;/span>&lt;span class="n">IUserRepository&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">default&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">virtual&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="nf">SaveUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>接著，建立依賴於此介面的服務類別（測試目標）。透過建構子注入（Constructor Injection）依賴性。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// include/UserService.h
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cp">#pragma once
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;IUserRepository.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;string&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">UserService&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">private&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">IUserRepository&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">repository_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">UserService&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">IUserRepository&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">repository&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">repository_&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">repository&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="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">bool&lt;/span> &lt;span class="nf">RegisterUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">name&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="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">empty&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">repository_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SaveUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="52-使用-googlemock-建立-mock-類別與測試">5.2 使用 GoogleMock 建立 Mock 類別與測試
&lt;/h3>&lt;p>使用 GoogleMock 的 &lt;code>MOCK_METHOD&lt;/code> 巨集將介面 Mock 化。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// tests/UserServiceTest.cpp
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;gtest/gtest.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;gmock/gmock.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;UserService.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;IUserRepository.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="o">::&lt;/span>&lt;span class="n">testing&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="o">::&lt;/span>&lt;span class="n">testing&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 建立 Mock 類別
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">class&lt;/span> &lt;span class="nc">MockUserRepository&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="k">public&lt;/span> &lt;span class="n">IUserRepository&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">MOCK_METHOD&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">bool&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SaveUser&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">override&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">TEST&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">UserServiceTest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">RegistersValidUserSuccessfully&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="cl"> &lt;span class="n">MockUserRepository&lt;/span> &lt;span class="n">mockRepo&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">UserService&lt;/span> &lt;span class="nf">service&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mockRepo&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 設定期望值：期待 SaveUser 被以 (1, &amp;#34;Kenji&amp;#34;) 呼叫 1 次，並回傳 true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">EXPECT_CALL&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mockRepo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SaveUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Kenji&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Times&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">WillOnce&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Return&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">true&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 執行測試目標
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">service&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">RegisterUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Kenji&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 斷言
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">EXPECT_TRUE&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="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">TEST&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">UserServiceTest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">RejectsEmptyNameWithoutCallingRepository&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="cl"> &lt;span class="n">MockUserRepository&lt;/span> &lt;span class="n">mockRepo&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">UserService&lt;/span> &lt;span class="nf">service&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mockRepo&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 名字為空時，期待 SaveUser 一次也不會被呼叫
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">EXPECT_CALL&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mockRepo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SaveUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Times&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">bool&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">service&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">RegisterUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">EXPECT_FALSE&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="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如此一來，透過使用 GoogleMock，就能夠準確地驗證「目標類別是否與依賴對象正確地互動」。&lt;/p>
&lt;h2 id="6-程式碼覆蓋率的測量與視覺化">6. 程式碼覆蓋率的測量與視覺化
&lt;/h2>&lt;p>在寫完測試後，為了客觀評估專案中哪部分被測試執行過（被覆蓋），我們需要測量&lt;strong>程式碼覆蓋率（Code Coverage）&lt;/strong>。程式碼覆蓋率（$Coverage$）以下列公式表示：&lt;/p>
$$ Coverage = \left( \frac{L_{executed}}{L_{total}} \right) \times 100 \ (\%) $$&lt;p>這裡的 $L_{executed}$ 是測試中執行的程式碼行數，$L_{total}$ 是專案整體的程式碼行數。&lt;/p>
&lt;p>如果是使用 GCC 或 Clang，可以使用 &lt;code>gcov&lt;/code> 與 &lt;code>lcov&lt;/code> 工具來測量覆蓋率。&lt;/p>
&lt;h3 id="61-在-cmake-中加入覆蓋率選項">6.1 在 CMake 中加入覆蓋率選項
&lt;/h3>&lt;p>為了測量覆蓋率，需要專用的編譯器旗標。在 &lt;code>CMakeLists.txt&lt;/code> 中加入以下設定。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 覆蓋率建置選項
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ENABLE_COVERAGE&lt;/span> &lt;span class="s2">&amp;#34;Enable coverage reporting&amp;#34;&lt;/span> &lt;span class="s">OFF&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ENABLE_COVERAGE&lt;/span> &lt;span class="s">AND&lt;/span> &lt;span class="s">CMAKE_CXX_COMPILER_ID&lt;/span> &lt;span class="s">MATCHES&lt;/span> &lt;span class="s2">&amp;#34;GNU|Clang&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">message&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">STATUS&lt;/span> &lt;span class="s2">&amp;#34;Coverage enabled&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">target_compile_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">core_lib&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">--coverage&lt;/span> &lt;span class="s">-O0&lt;/span> &lt;span class="s">-g&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">target_link_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">core_lib&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">--coverage&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">target_compile_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">unit_tests&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">--coverage&lt;/span> &lt;span class="s">-O0&lt;/span> &lt;span class="s">-g&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">target_link_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">unit_tests&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">--coverage&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">endif&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="62-覆蓋率報告的產生步驟">6.2 覆蓋率報告的產生步驟
&lt;/h3>&lt;p>在建置時啟用旗標，並在執行測試後使用 &lt;code>lcov&lt;/code> 輸出 HTML 報告。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 啟用覆蓋率選項並建置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir build &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">cd&lt;/span> build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake .. -DENABLE_COVERAGE&lt;span class="o">=&lt;/span>ON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 執行測試&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ctest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 收集覆蓋率資料 (執行 lcov)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">lcov --capture --directory . --output-file coverage.info
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. 排除系統標頭檔與外部函式庫（如 GoogleTest）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">lcov --remove coverage.info &lt;span class="s1">&amp;#39;/usr/*&amp;#39;&lt;/span> &lt;span class="s1">&amp;#39;*/_deps/*&amp;#39;&lt;/span> &lt;span class="s1">&amp;#39;*/tests/*&amp;#39;&lt;/span> --output-file coverage.info
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 5. 產生 HTML 報告&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">genhtml coverage.info --output-directory coverage_report
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>使用瀏覽器打開產生的 &lt;code>coverage_report/index.html&lt;/code>，可以透過綠色與紅色視覺化地以原始碼為單位顯示哪一行被執行過，有助於找出測試遺漏的部分（尋找 Coverage Hole）。&lt;/p>
&lt;h2 id="7-c-專案中-tdd-的課題與最佳實踐">7. C++ 專案中 TDD 的課題與最佳實踐
&lt;/h2>&lt;p>在 C++ 專案導入 TDD 時，存在著特有的課題。&lt;/p>
&lt;h3 id="71-建置時間編譯時間的增加">7.1 建置時間（編譯時間）的增加
&lt;/h3>&lt;p>由於 C++ 大量使用樣板（Template）以及大型標頭檔的引入（Include），編譯時間往往會變長。TDD 的「Red-Green-Refactor」循環必須快速進行，因此建置時間的延遲是致命的。
&lt;strong>對策&lt;/strong>: 善用前置宣告（Forward Declaration）與 Pimpl（Pointer to implementation）慣用語，將標頭檔的依賴關係降到最低。此外，導入如 Ccache 等建置快取工具也非常有效。&lt;/p>
&lt;h3 id="72-在遺留程式碼legacy-code中導入-tdd">7.2 在遺留程式碼（Legacy Code）中導入 TDD
&lt;/h3>&lt;p>要在現有龐大的單體式（Monolithic）程式碼中事後應用 TDD 是非常困難的。
&lt;strong>對策&lt;/strong>: 建議不要一開始就重寫全部，而是從新增功能的部分或是修復錯誤的地方（童子軍規則）階段性地加入測試，一步步將程式碼庫納入 TDD 的控制之下（即《Working Effectively with Legacy Code》書中的手法）。&lt;/p>
&lt;h2 id="8-作為軟體設計的-tdd">8. 作為軟體設計的 TDD
&lt;/h2>&lt;p>TDD 是維持程式碼品質的安全網，同時也是提升 C++ 程式碼設計的驅動力。為了撰寫測試而強制進行的依賴注入（DI），其結果會降低類別間的耦合度（Coupling），並提高內聚度（Cohesion）。&lt;/p>
&lt;p>在重構時，意識到循環複雜度（McCabe&amp;rsquo;s Cyclomatic Complexity）也是很重要的。&lt;/p>
$$ M = E - N + 2P $$&lt;p>（$M$: 複雜度, $E$: 邊數, $N$: 節點數, $P$: 連通元件數）&lt;/p>
&lt;p>因為有了測試，就可以在不畏懼破壞性變更的情況下，執行降低複雜度的函式分割或替換成多型（Polymorphism）。&lt;/p>
&lt;h2 id="總結">總結
&lt;/h2>&lt;p>本文詳細解說了如何在 C++ 專案中使用 GoogleTest 與 GoogleMock 導入測試驅動開發（TDD）的方法。&lt;/p>
&lt;ol>
&lt;li>使用 &lt;strong>CMake FetchContent&lt;/strong> 的現代化專案配置&lt;/li>
&lt;li>實踐 &lt;strong>Red-Green-Refactor&lt;/strong> 循環&lt;/li>
&lt;li>使用 &lt;strong>GoogleMock 與 依賴注入 (DI)&lt;/strong> 進行介面 Mock 化&lt;/li>
&lt;li>透過 &lt;strong>gcov/lcov&lt;/strong> 將測試覆蓋率視覺化&lt;/li>
&lt;/ol>
&lt;p>雖然 TDD 是需要時間學習的方法，但在像是 C++ 這種要求兼顧效能與安全性的系統程式設計中，其投資報酬率是無可估量的。請務必在下一個專案中慢慢實踐 TDD，獲得堅固且易於維護的 C++ 程式碼。&lt;/p></description></item></channel></rss>