<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Testing on kenji.blog</title><link>http://kenji.blog/zh-cn/categories/testing/</link><description>Recent content in Testing on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>kenjinote</copyright><lastBuildDate>Sat, 12 Sep 2026 21:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/zh-cn/categories/testing/index.xml" rel="self" type="application/rss+xml"/><item><title>在C++项目（如GoogleTest等）中引入测试驱动开发（TDD）的方法</title><link>http://kenji.blog/zh-cn/p/tdd-cpp-googletest-introduction/</link><pubDate>Sat, 12 Sep 2026 21:00:00 +0900</pubDate><guid>http://kenji.blog/zh-cn/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++项目中引入&lt;strong>测试驱动开发（Test-Driven Development: TDD）&lt;strong>的方法。我们将全面涵盖单元测试框架&lt;/strong>GoogleTest&lt;/strong>和Mock框架&lt;strong>GoogleMock&lt;/strong>的使用方法，以及如何使用构建系统&lt;strong>CMake&lt;/strong>进行现代化的配置，甚至包括代码覆盖率的测量方法。&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-bug发现延迟导致的成本增加">1.2 Bug发现延迟导致的成本增加
&lt;/h3>&lt;p>在软件工程中，众所周知，发现Bug的时间越靠后，修复它的成本就会呈指数级增加。这种成本增加模型可以用以下数学公式来近似表示：&lt;/p>
$$ Cost(t) = C_0 \times e^{k \cdot t} $$&lt;p>这里，$Cost(t)$ 是时间 $t$ 时的修复成本，$C_0$ 是Bug刚被引入时的修复成本（基线），$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的魅力在于其丰富的断言、强大的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> 模块。这样可以省去管理子模块或提前安装库的麻烦。&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> 的骨架，以及测试代码。&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>在这个例子中代码非常简单，但随着需求的复杂化，在重构阶段我们需要提高代码的可读性，或者改善性能。测试代码本身也是重构的对象。例如，可以考虑引入测试夹具（&lt;code>testing::Test&lt;/code>）来实现初始化的通用化。&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>。理解它们之间的区别对于编写健壮的测试非常重要。&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;strong>中断&lt;/strong>当前测试函数的执行（致命失败）。适用于后续的验证已经失去意义的情况（例如：在确认指针不为 &lt;code>nullptr&lt;/code> 之后立即对其进行解引用的情况）。&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>代码覆盖率&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>，可以直观地看到以源代码行级别用绿色和红色高亮显示的执行情况，有助于发现测试遗漏（识别覆盖率盲点）。&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++大量使用模板并包含大规模头文件，编译时间往往较长。而TDD的“Red-Green-Refactor”循环需要快速进行，因此构建时间的延迟是致命的。
&lt;strong>对策&lt;/strong>: 活用前向声明（Forward Declaration）和 Pimpl（Pointer to implementation）惯用法，将头文件的依赖关系降至最低。此外，引入如 Ccache 等构建缓存工具也非常有效。&lt;/p>
&lt;h3 id="72-在遗留代码中引入-tdd">7.2 在遗留代码中引入 TDD
&lt;/h3>&lt;p>想要在庞大的现有单体代码中事后应用TDD是极其困难的。
&lt;strong>对策&lt;/strong>: 建议采用《修改代码的艺术 (Working Effectively with Legacy Code)》中的方法，不要一开始就全部重写，而是从添加新功能的部分或修复Bug的地方（童子军规则）逐步添加测试，一点一点地将代码库置于TDD的控制之下。&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>因为有测试的存在，为了降低这种复杂度而进行函数拆分或替换为多态时，我们就可以毫无对破坏性变更的恐惧地去执行。&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>