跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++ 测试与调试:从框架到工具链的实践备忘

从单元测试到集成调试的实战手册,覆盖 Google Test 与 Catch2 的写法、GDB 和 Visual Studio 调试器的常用操作,并通过一个计算器项目展示如何组织测试、构建与运行。同时给出 Mock、性能分析、静态检查和 CI 集成的进阶方向。

月亮邮递员发布于 2026/6/27更新于 2026/10/752 浏览
C++ 测试与调试:从框架到工具链的实践备忘

在这里插入图片描述

写 C++ 久了就会发现,测试和调试不是附属品,而是开发节奏的一部分。测试帮你把预期行为固定下来,重构时更有底气;调试则是在代码跑偏时,把真相从变量和调用栈里挖出来。这里整理一些个人常用的套路,不追求大而全,但求上手能打。

测试的分层与原则

测试通常按粒度分层,每层目标不同:

  • 单元测试:盯单个函数或类,隔离外部依赖。
  • 集成测试:验证模块间交互,像 Calculator 和 Parser 能不能对上暗号。
  • 系统测试:端到端模拟真实环境,保证整个应用行为正常。
  • 验收测试:确认产物符合需求,这一步经常由 QA 主导。
  • 性能测试:压一压瓶颈,看看响应时间和资源占用。

无论哪一层,几条原则值得反复念叨:测试要早写,别等到提测前才补;用例要独立,不能依赖执行顺序;尽量自动化,否则没人愿意跑;结果要稳定,不是概率游戏。

单元测试框架

C++ 的单元测试框架多到选择困难,但 Google Test 和 Catch2 基本覆盖了大部分场景。Google Test 功能全、社区大,Catch2 语法轻、单头文件就能用。我一般看项目环境—如果已经有 CMake 和 Google Test 集成,就用前者;如果是个小工具,Catch2 拖进去就写。

Google Test

安装不复杂:

# Ubuntu/Debian
sudo apt-get install libgtest-dev

# macOS (Homebrew)
brew install googletest

典型写法如下,断言宏丰富,EXPECT_* 失败不中止当前用例,ASSERT_* 则直接停。

#include <gtest/gtest.h>
#include "MyClass.h"

TEST(MyClassTest, ConstructorTest) {
    MyClass obj;
    EXPECT_EQ(obj.getValue(), 0);
}

TEST(MyClassTest, SetGetValueTest) {
    MyClass obj;
    obj.setValue(42);
    EXPECT_EQ(obj.getValue(), 42);
}

TEST(MyClassTest, AddTest) {
    MyClass obj;
    obj.setValue(10);
    int result = obj.add(20);
    EXPECT_EQ(result, 30);
    EXPECT_EQ(obj.getValue(), 30);
}

TEST(MyClassTest, SubtractTest) {
    MyClass obj;
    obj.setValue(50);
    int result = obj.subtract(20);
    EXPECT_EQ(result, 30);
    EXPECT_EQ(obj.getValue(), 30);
}

int main(int argc, char** argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

Catch2

Catch2 的 TEST_CASE 更灵活,可以直接打标签方便分组。安装同样简单:

# Ubuntu/Debian
sudo apt-get install catch2

# macOS (Homebrew)
brew install catch2

用法举例:

#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
#include "MyClass.h"

TEST_CASE("MyClass Constructor Test", "[constructor]") {
    MyClass obj;
    REQUIRE(obj.getValue() == 0);
}

TEST_CASE("MyClass Set and Get Value Test", "[setget]") {
    MyClass obj;
    obj.setValue(42);
    REQUIRE(obj.getValue() == 42);
}

TEST_CASE("MyClass Add Test", "[add]") {
    MyClass obj;
    obj.setValue(10);
    int result = obj.add(20);
    REQUIRE(result == 30);
    REQUIRE(obj.getValue() == 30);
}

TEST_CASE("MyClass Subtract Test", "[subtract]") {
    MyClass obj;
    obj.setValue(50);
    int result = obj.subtract(20);
    REQUIRE(result == 30);
    REQUIRE(obj.getValue() == 30);
}

调试工具

调试器的选择跟着操作系统走。Linux 上用 GDB,命令行虽然有时劝退,但必备命令背熟之后效率很高;Windows 下 Visual Studio 的图形化调试体验更友好,断点拖拽、变量监视都很直观。

GDB 速查

编译时别忘了 -g,否则符号信息进不来。

# 编译并进入 GDB
g++ -g program.cpp -o program
gdb program

# 几个常用命令
break main          # 在 main 入口设断点
run                 # 启动
next                # 步过(不进入函数)
step                # 步入(进入函数)
print variable      # 打印变量
watch variable      # 监视变量变化
backtrace           # 查看调用栈
continue            # 继续执行
quit                # 退出

调试时可以在关键位置打上 break,print 变量看看是不是预期值。比如下面这个简单的程序:

#include <iostream>
#include "MyClass.h"

int main() {
    MyClass obj;
    obj.setValue(42);
    int result = obj.add(20);
    std::cout << "结果:" << result << std::endl;
    return 0;
}

跑起来后在 setValue 后面停下,看一眼 obj.getValue(),确认 42 进去了再继续。

Visual Studio 调试器

在行号左侧点一下断点,F5 开始调试,程序停住后就能在局部变量窗口和调用堆栈里探索。F10 / F11 分别对应逐过程、逐语句,和 GDB 的 next/step 差不多。比较复杂的多线程程序里,用 VS 的并行线程窗口去追数据竞争会省很多时间。

集成测试

单元测试只保证零件没问题,集成测试才看零件组装后会不会互相扯皮。常见的策略有自顶向下、自底向上,或者三明治混合。实际项目中很少死守一种,通常哪种能先跑通就用哪种。

下面是一个简单的集成测试示例,把 Calculator 和 Parser 绑在一起:

#include <gtest/gtest.h>
#include "Calculator.h"
#include "Parser.h"

TEST(IntegrationTest, CalculateTest) {
    Calculator calculator;
    Parser parser;

    std::string expression = "10 + 20";
    int result = parser.parse(expression);
    EXPECT_EQ(result, 30);

    expression = "10 * 20";
    result = parser.parse(expression);
    EXPECT_EQ(result, 200);

    expression = "(10 + 20) * 3";
    result = parser.parse(expression);
    EXPECT_EQ(result, 90);
}

注意这个集成测试直接用 Google Test 来写,也可以放到 Catch2 里,看团队统一选哪个。

综合案例:带测试的计算器

下面用一个简单的计算器项目把测试、调试串起来。项目结构尽量保持清晰:

CalculatorProject/
├── include/
│   ├── Calculator.h
│   └── Parser.h
├── src/
│   ├── Calculator.cpp
│   ├── Parser.cpp
│   └── main.cpp
├── tests/
│   ├── CalculatorTest.cpp
│   ├── ParserTest.cpp
│   └── IntegrationTest.cpp
└── CMakeLists.txt

头文件与实现

Calculator.h

#ifndef CALCULATOR_H
#define CALCULATOR_H

class Calculator {
public:
    static int add(int a, int b);
    static int subtract(int a, int b);
    static int multiply(int a, int b);
    static int divide(int a, int b);
};

#endif // CALCULATOR_H

Calculator.cpp

#include "Calculator.h"
#include <stdexcept>

int Calculator::add(int a, int b) {
    return a + b;
}

int Calculator::subtract(int a, int b) {
    return a - b;
}

int Calculator::multiply(int a, int b) {
    return a * b;
}

int Calculator::divide(int a, int b) {
    if (b == 0) {
        throw std::invalid_argument("除数不能为零");
    }
    return a / b;
}

Parser.h

#ifndef PARSER_H
#define PARSER_H

#include <string>
#include "Calculator.h"

class Parser {
public:
    int parse(const std::string& expression);
};

#endif // PARSER_H

Parser.cpp

#include "Parser.h"
#include <sstream>

int Parser::parse(const std::string& expression) {
    std::istringstream iss(expression);
    int a, b;
    char op;
    iss >> a >> op >> b;
    switch (op) {
    case '+': return Calculator::add(a, b);
    case '-': return Calculator::subtract(a, b);
    case '*': return Calculator::multiply(a, b);
    case '/': return Calculator::divide(a, b);
    default: throw std::invalid_argument("无效的运算符");
    }
}

main.cpp

#include <iostream>
#include "Parser.h"

int main() {
    Parser parser;
    try {
        std::string expression;
        std::cout << "请输入表达式(例如:10 + 20): ";
        std::getline(std::cin, expression);
        int result = parser.parse(expression);
        std::cout << "结果:" << result << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "错误:" << e.what() << std::endl;
        return 1;
    }
    return 0;
}

测试代码

CalculatorTest.cpp

#include <gtest/gtest.h>
#include "Calculator.h"

TEST(CalculatorTest, AddTest) {
    EXPECT_EQ(Calculator::add(10, 20), 30);
    EXPECT_EQ(Calculator::add(-10, 20), 10);
    EXPECT_EQ(Calculator::add(0, 0), 0);
}

TEST(CalculatorTest, SubtractTest) {
    EXPECT_EQ(Calculator::subtract(20, 10), 10);
    EXPECT_EQ(Calculator::subtract(10, 20), -10);
    EXPECT_EQ(Calculator::subtract(0, 0), 0);
}

TEST(CalculatorTest, MultiplyTest) {
    EXPECT_EQ(Calculator::multiply(10, 20), 200);
    EXPECT_EQ(Calculator::multiply(-10, 20), -200);
    EXPECT_EQ(Calculator::multiply(0, 10), 0);
}

TEST(CalculatorTest, DivideTest) {
    EXPECT_EQ(Calculator::divide(20, 10), 2);
    EXPECT_EQ(Calculator::divide(-20, 10), -2);
    EXPECT_THROW(Calculator::divide(10, 0), std::invalid_argument);
}

ParserTest.cpp

#include <gtest/gtest.h>
#include "Parser.h"

TEST(ParserTest, ParseTest) {
    Parser parser;
    EXPECT_EQ(parser.parse("10 + 20"), 30);
    EXPECT_EQ(parser.parse("20 - 10"), 10);
    EXPECT_EQ(parser.parse("10 * 20"), 200);
    EXPECT_EQ(parser.parse("20 / 10"), 2);
    EXPECT_THROW(parser.parse("10 / 0"), std::invalid_argument);
    EXPECT_THROW(parser.parse("10 a 20"), std::invalid_argument);
}

IntegrationTest.cpp

#include <gtest/gtest.h>
#include "Parser.h"

TEST(IntegrationTest, CalculateTest) {
    Parser parser;
    EXPECT_EQ(parser.parse("10 + 20"), 30);
    EXPECT_EQ(parser.parse("20 - 10"), 10);
    EXPECT_EQ(parser.parse("10 * 20"), 200);
    EXPECT_EQ(parser.parse("20 / 10"), 2);
    EXPECT_THROW(parser.parse("10 / 0"), std::invalid_argument);
}

构建与运行

根目录 CMakeLists.txt:

cmake_minimum_required(VERSION 3.10)
project(CalculatorProject)

set(CMAKE_CXX_STANDARD 17)
include_directories(include)

add_library(Calculator src/Calculator.cpp)
add_library(Parser src/Parser.cpp)

add_executable(CalculatorApp src/main.cpp)
target_link_libraries(CalculatorApp Calculator Parser)

add_subdirectory(tests)

tests/CMakeLists.txt:

cmake_minimum_required(VERSION 3.10)
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

add_executable(CalculatorTests CalculatorTest.cpp ParserTest.cpp IntegrationTest.cpp)
target_link_libraries(CalculatorTests ${GTEST_LIBRARIES} Calculator Parser pthread)
include(GoogleTest)
gtest_discover_tests(CalculatorTests)

编译与测试:

mkdir -p build && cd build
cmake ..
make
ctest

如果一切正常,会看到所有测试通过。这时候改完代码就能随手跑一遍测试,心里不慌。

下一步

测试与调试的实践远不止这些。如果项目里有大量外部依赖,可以试试 Google Mock 做模拟对象;关心性能就用 Google Benchmark 配合 gcov 看覆盖率;静态检查用 Clang Static Analyzer 或 clang-tidy 提前扫出潜在问题;最后把测试接入 GitHub Actions 等 CI 平台,每次 push 自动跑,才能真正把质量嵌进开发流程里。

目录

  1. 测试的分层与原则
  2. 单元测试框架
  3. Google Test
  4. Ubuntu/Debian
  5. macOS (Homebrew)
  6. Catch2
  7. Ubuntu/Debian
  8. macOS (Homebrew)
  9. 调试工具
  10. GDB 速查
  11. 编译并进入 GDB
  12. 几个常用命令
  13. Visual Studio 调试器
  14. 集成测试
  15. 综合案例:带测试的计算器
  16. 头文件与实现
  17. 测试代码
  18. 构建与运行
  19. 下一步

更多推荐文章

查看全部
  • 基于 Python 和 YOLOv11 训练自定义数据集
  • 基于 MCP 协议的 Claude 智能体天气服务落地示例
  • Whisper.cpp 量化版本清单与 ggml 格式模型下载
  • AutoResearch:Karpathy 用 630 行 Python 代码实现 AI 自主研究框架
  • 基于 steamdt_crawler.py 项目学习 Python 爬虫实战
  • STL 缩略图插件:Windows 文件资源管理器 3D 预览方案
  • Stable Diffusion 镜像交付标准:Dockerfile 透明与构建可追溯
  • Capacitor 跨平台 Web 原生应用开发及鸿蒙适配指南
  • Web 开发者构建多模态 Agent 图像识别技能:JS+Python 全栈实践
  • 3D Max VR 渲染器局部渲染设置教程
  • AI 时代的技术民主化:为什么文科生可能成为最大受益者?
  • Linux Shell 编程之条件语句详解
  • 链表的应用:内存管理与缓存淘汰算法
  • Claude Code 安装与使用实录
  • 钉钉宜搭低代码方案:从快速搭建到深度定制的技术实践
  • .NET 集成 GoView 低代码可视化大屏实战指南
  • 基于 YOLO12 的无人机航拍目标检测系统实战
  • 2023 年全国职业院校技能大赛网络建设与运维赛项样题 (一) 解析
  • 分布式配置中心深度解析:Spring Cloud Config 与 Apollo 架构对比
  • 麦橘超然 Flux 本地部署与低显存优化实战

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online