C++ 项目测试全指南:从 0 基础到落地实操

很多刚入门开发的朋友都会有一个灵魂拷问:我主程序能跑不就行了?为什么要花时间写测试?

我刚写代码时也这么想:测试是大公司的流程化要求,我一个人写小项目,主程序跑通、测完删掉测试代码发布,不就完事了?直到踩了无数次坑才明白:测试不是给老板走的流程,是给开发者自己上的「代码保险」------ 它能让你改代码不心虚,上线不失眠,不用半夜爬起来修用户反馈的低级 bug。

这篇文章完全贴合个人 / 中小型 C++ 项目的场景,不讲空泛的大公司理论,只讲你能直接落地、能解决实际问题的测试知识点,全程结合你熟悉的 CMake 项目结构,看完就能上手。


一、先搞懂核心:测试到底在测什么?

测试的本质,不是 "证明你的代码是对的",而是 **"提前发现你的代码在哪些场景下会出错"**。

我们写的业务代码,是给用户解决问题的;而测试代码,是给我们自己兜底的 ------ 它专门盯着代码的 "死角":正常情况能不能跑?边界情况会不会崩?改了新功能会不会把旧功能搞坏?

针对一个完整的 C++ 项目,测试分为 3 个核心层级,从下到上覆盖了代码的全链路,我们用你熟悉的「加法计算器项目」举例子,一眼就能看懂:

测试层级 核心目标 测什么? 对应项目里的内容
单元测试 保证每个「零件」是好的 单独的函数、类、模块的逻辑是否正确 utils.cpp里的add函数:给固定输入,看输出是否符合预期
集成测试 保证「零件拼在一起」能正常工作 多个模块、库之间的联动、数据传递是否正常 测主程序调用utils库的链路:主程序能不能正常拿到add函数的结果,有没有链接错误、头文件找不到的问题
端到端测试(E2E 测试) 保证「整车能正常开」 整个程序从用户输入到输出的完整流程是否符合预期 你自己当用户,运行编译好的主程序,输入两个数,看输出结果对不对 ------ 也就是「主程序测试」

新手必记的分层原则

  • 单元测试:能自动化就自动化,用工具帮你跑,不用每次手动改代码;
  • 集成测试:中小型项目可以简化,只要单元测试没问题,编译链接能过,基本不会出大问题;
  • 端到端测试(主程序测试):个人项目手动测就行,不用写复杂的自动化脚本,你自己当用户跑一遍,比写脚本快得多。

二、C++ 项目测试核心工具链:GoogleTest + CTest 到底是什么?

你之前一直在问 CTest,这里彻底把这两个工具的分工讲透,再也不会混淆:

1. GoogleTest(简称 GTest):写测试用例的「考卷模板」

GoogleTest 是谷歌开源的 C++ 单元测试框架,也是目前行业的事实标准。它的作用,是给你提供一套简单的语法,让你不用自己写一堆std::cout判断对错,就能快速写出测试用例。

比如你要测add函数,用 GTest 只需要这么写:

cpp 复制代码
#include <gtest/gtest.h>
#include "utils.h"

// 测试用例:TEST(测试套件名, 测试用例名)
TEST(AddFunctionTest, NormalInput) {
    // EXPECT_EQ:预期两个值相等,不等就标记测试失败,程序继续跑
    EXPECT_EQ(add(1, 2), 3);
    EXPECT_EQ(add(-1, 1), 0);
    EXPECT_EQ(add(0, 0), 0);
}

TEST(AddFunctionTest, BoundaryInput) {
    // ASSERT_EQ:预期两个值相等,不等就直接终止当前测试用例
    ASSERT_EQ(add(INT_MAX, 0), INT_MAX);
}

它自带了EXPECT_EQASSERT_TRUE等几十种判断宏,不用你自己写if-else判断结果,测试失败会直接告诉你 "哪一行错了、预期值是什么、实际值是什么",不用人眼盯着输出找问题。

2. CTest:调度测试的「监考老师」

CTest 是 CMake 自带的测试运行器,它本身不写测试逻辑,核心作用只有 3 个:

  1. 自动找到你项目里所有的测试程序(比如上面用 GTest 写的测试代码编译出来的可执行文件);
  2. 一键运行所有测试用例,不用你挨个手动执行;
  3. 生成测试报告:告诉你一共跑了多少个用例、通过了多少、失败了多少、哪个用例在哪一行错了。

3. 两者的完美配合

  • GTest 负责出考卷:写清楚要测什么、什么算对、什么算错;
  • CTest 负责监考判分:一键跑完全部考卷,给你一个最终结果。

这也是为什么 CMake 模板里,测试目录的配置会同时用到这两个工具 ------GTest 写用例,CTest 跑用例,是 C++ 项目最省心的测试组合。


三、手动测试 vs 自动化测试:到底该怎么选?

问 "我直接用主程序测试难道不行吗?",答案是:完全可以,但要看你的项目场景。没有绝对的好坏,只有适合不适合。

我们把两种方式的优缺点、适用场景列清楚,你一眼就能知道自己该用哪种:

1. 手动测试(直接用主程序测)

就是你之前的思路:在主程序里加几行打印代码,人眼看结果对不对,测完删掉测试代码,编译发布。

  • ✅ 优点:简单省事,不用学任何测试框架,不用写额外的代码,小项目 1 分钟就能测完;
  • ❌ 缺点:每次改代码都要手动加删代码、人眼看结果,测试点多了容易漏,改新功能容易忘了测旧功能;
  • 🎯 适用场景:
    • 项目特别小(几十行代码,1-2 个功能);
    • 个人一次性项目(课程作业、写完就扔的小工具,以后不会再改);
    • 测试点少于 5 个,人眼看一眼就能判断对错。

2. 自动化测试(GTest+CTest)

把测试代码写在单独的文件里,和主程序完全分离,一键运行,自动判断对错。

  • ✅ 优点:不用反复改主程序,改完代码一键跑完全部测试,不会漏测旧功能,能覆盖大量边界场景,多人协作时所有人都知道该测什么;
  • ❌ 缺点:需要花 10 分钟学基础语法,要写额外的测试代码,极小的项目会显得 "杀鸡用牛刀";
  • 🎯 适用场景:
    • 项目会长期维护、会频繁加新功能;
    • 核心功能的测试点超过 5 个,有边界情况、异常情况要测;
    • 多人协作开发,需要统一的测试标准。

给你的折中方案

不用非黑即白,中小型项目完全可以这么做:

  • 核心功能、会频繁修改的函数:写自动化单元测试,用 GTest+CTest 兜底,保证改代码不会搞坏;
  • 主程序的完整流程:手动跑一遍端到端测试,不用写自动化脚本,省事高效。

四、中小型项目测试最佳实践(直接照着做就行)

很多新手对测试的困惑是:"我到底要写多少测试?写到什么程度算够?",这里给你一套可落地的规则,不用纠结。

1. 先测核心,不测边角

不用追求 "100% 代码覆盖率"(就是每一行代码都被测到),中小型项目核心功能的覆盖率能到 80% 就足够了。

  • 优先测:项目里最核心、用户最常用、改得最频繁的功能(比如计算器的加减乘除);
  • 不用测:不会改的、边角的、极端场景才会用到的功能(比如计算器的 "历史记录导出",不是核心功能,不用优先测)。

2. 测试用例要覆盖 3 种场景

一个合格的测试用例,不能只测 "正常输入",要覆盖这 3 种场景,才能提前发现 90% 的 bug:

  1. 正常场景 :用户最常用的输入(比如add(1,2));
  2. 边界场景 :输入的最大值、最小值、空值(比如add(INT_MAX, 0)add(INT_MIN, -1));
  3. 异常场景:非法的、用户可能误输入的内容(比如除法函数的除数为 0)。

3. 测试代码要比业务代码更简单

新手最容易踩的坑:为了写测试,写了一堆复杂的逻辑,结果测试代码本身出了 bug,反而怀疑业务代码有问题。

  • 记住:测试用例一定要 **"输入固定,输出固定"**,不要在测试代码里写循环、复杂的判断,就用最简单的固定值判断对错;
  • 一个测试用例只测一件事:不要在一个测试用例里既测加法、又测乘法,分开写,哪个错了一眼就能找到。

4. 先改 bug,再补测试

每次用户反馈了 bug,或者你自己发现了 bug,修复完之后,一定要补一个对应的测试用例 ------ 保证这个 bug 以后再也不会出现。这是测试最有价值的场景之一。


五、新手写测试的避坑指南

  1. 不要为了测试而测试:测试是为了解放你的精力,不是给你增加负担。如果一个测试写起来特别复杂,反而让你不想改代码了,那不如不写。
  2. 不要把测试代码和业务代码混在一起 :测试代码一定要放在单独的tests目录里,和主程序完全分离,不要在主程序里留一堆测试代码,发布的时候忘了删,给用户造成困扰。
  3. 不要依赖手动测试的 "记忆力":不要觉得 "我这次测过了,下次改代码肯定记得再测一遍"------ 人的记忆力是不可靠的,自动化测试才是可靠的。
  4. 不要测第三方库:你用的 spdlog、OpenCV 这些成熟的第三方库,人家自己已经测过了,你不用再测库的函数,只需要测你自己写的逻辑。

六、完整实操示例:从 0 到 1 跑通 C++ 项目测试

这里结合之前给你的简化 CMake 模板,给你一套完整的、复制就能跑通的测试示例,全程 5 分钟就能搞定。

1. 项目结构

bash 复制代码
ToolKitProject/                # 扩展后的项目名:实用工具集
├── CMakeLists.txt             # 根目录CMake配置(微调,支持多库)
├── include/                   # 头文件目录(按工具类型分文件夹)
│   ├── math/                  # 数学相关头文件
│   │   ├── calc.h             # 之前的计算器头文件
│   │   └── math_stats.h       # 【新增】数学统计头文件
│   ├── string/                # 【新增】字符串处理头文件
│   │   └── string_utils.h
│   └── array/                 # 【新增】数组操作头文件
│       └── array_utils.h
├── src/                       # 源码目录
│   ├── CMakeLists.txt
│   ├── utils/                 # 工具库源码(按类型分子目录)
│   │   ├── CMakeLists.txt     # 【调整】总控所有工具库
│   │   ├── math/              # 数学工具实现
│   │   │   ├── CMakeLists.txt
│   │   │   ├── calc.cpp
│   │   │   └── math_stats.cpp
│   │   ├── string/            # 字符串工具实现
│   │   │   ├── CMakeLists.txt
│   │   │   └── string_utils.cpp
│   │   └── array/             # 数组工具实现
│   │       ├── CMakeLists.txt
│   │       └── array_utils.cpp
│   └── app/                   # 可执行文件(简单演示工具用法)
│       ├── CMakeLists.txt
│       └── main.cpp
└── tests/                     # 测试目录(按工具类拆分测试文件)
    ├── CMakeLists.txt         # 【调整】总控所有测试
    ├── test_calc.cpp          # 之前的计算器测试
    ├── test_math_stats.cpp    # 【新增】数学统计测试
    ├── test_string_utils.cpp  # 【新增】字符串工具测试
    └── test_array_utils.cpp   # 【新增】数组工具测试

2. 业务代码

2.1. 根目录 CMakeLists.txt(几乎不用改,兼容多库)

bash 复制代码
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(ToolKitProject VERSION 1.0.0 LANGUAGES CXX)

# 全局C++17标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# 构建类型默认Release
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()

# 输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

# 测试开关
option(BUILD_TESTS "Build unit tests" ON)

# 添加子目录
add_subdirectory(src)
if(BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

2.2. 第一组工具:基础计算器(保留并完善)

include/math/calc.h
cpp 复制代码
#ifndef MATH_CALC_H
#define MATH_CALC_H
#include <stdexcept>

// 加法
int add(int a, int b);
// 除法(带异常)
int divide(int a, int b);

#endif // MATH_CALC_H
src/utils/math/calc.cpp
cpp 复制代码
#include "math/calc.h"

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

int divide(int a, int b) {
    if (b == 0) {
        throw std::invalid_argument("除数不能为0");
    }
    return a / b;
}
src/utils/math/CMakeLists.txt
bash 复制代码
# 生成数学基础库
add_library(math_calc STATIC calc.cpp)
target_include_directories(math_calc PUBLIC ${CMAKE_SOURCE_DIR}/include)

2.3. 第二组工具:数学统计(新增,重点展示浮点数测试)

这个工具类用于计算方差、标准差,重点展示 GTest 如何处理浮点数精度问题 (不能直接用EXPECT_EQ)。

include/math/math_stats.h
cpp 复制代码
#ifndef MATH_STATS_H
#define MATH_STATS_H
#include <vector>
#include <stdexcept>

// 计算平均值
double mean(const std::vector<double>& data);
// 计算方差(样本方差)
double variance(const std::vector<double>& data);
// 计算标准差(样本标准差)
double std_dev(const std::vector<double>& data);

#endif // MATH_STATS_H
src/utils/math/math_stats.cpp
cpp 复制代码
#include "math/math_stats.h"
#include <numeric>
#include <cmath>

double mean(const std::vector<double>& data) {
    if (data.empty()) {
        throw std::invalid_argument("数据不能为空");
    }
    double sum = std::accumulate(data.begin(), data.end(), 0.0);
    return sum / data.size();
}

double variance(const std::vector<double>& data) {
    if (data.size() < 2) {
        throw std::invalid_argument("数据至少需要2个");
    }
    double m = mean(data);
    double sum_sq = 0.0;
    for (double num : data) {
        sum_sq += (num - m) * (num - m);
    }
    return sum_sq / (data.size() - 1); // 样本方差除以n-1
}

double std_dev(const std::vector<double>& data) {
    return std::sqrt(variance(data));
}
src/utils/math/CMakeLists.txt(更新,加入统计库)
bash 复制代码
# 数学基础库
add_library(math_calc STATIC calc.cpp)
target_include_directories(math_calc PUBLIC ${CMAKE_SOURCE_DIR}/include)

# 【新增】数学统计库
add_library(math_stats STATIC math_stats.cpp)
target_include_directories(math_stats PUBLIC ${CMAKE_SOURCE_DIR}/include)

2.4. 第三组工具:字符串处理(新增,展示空值 / 边界测试)

include/string/string_utils.h
cpp 复制代码
#ifndef STRING_UTILS_H
#define STRING_UTILS_H
#include <string>
#include <vector>

// 转大写
std::string to_upper(const std::string& str);
// 判断是否以某子串开头
bool starts_with(const std::string& str, const std::string& prefix);
// 分割字符串(按分隔符分割)
std::vector<std::string> split(const std::string& str, char delimiter);

#endif // STRING_UTILS_H
src/utils/string/string_utils.cpp
cpp 复制代码
#include "string/string_utils.h"
#include <algorithm>
#include <sstream>

std::string to_upper(const std::string& str) {
    std::string result = str;
    std::transform(result.begin(), result.end(), result.begin(),
                   [](sslocal://flow/file_open?url=unsigned+char+c&flow_extra=eyJsaW5rX3R5cGUiOiJjb2RlX2ludGVycHJldGVyIn0=){ return std::toupper(c); });
    return result;
}

bool starts_with(const std::string& str, const std::string& prefix) {
    if (prefix.empty()) return true;
    if (str.length() < prefix.length()) return false;
    return str.substr(0, prefix.length()) == prefix;
}

std::vector<std::string> split(const std::string& str, char delimiter) {
    std::vector<std::string> result;
    std::stringstream ss(str);
    std::string token;
    while (std::getline(ss, token, delimiter)) {
        if (!token.empty()) { // 跳过空token
            result.push_back(token);
        }
    }
    return result;
}
src/utils/string/CMakeLists.txt
复制代码
add_library(string_utils STATIC string_utils.cpp)
target_include_directories(string_utils PUBLIC ${CMAKE_SOURCE_DIR}/include)

2.5. 第四组工具:数组操作(新增,展示 STL 容器测试)

include/array/array_utils.h
cpp 复制代码
#ifndef ARRAY_UTILS_H
#define ARRAY_UTILS_H
#include <vector>
#include <stdexcept>

// 找最大值
int max(const std::vector<int>& arr);
// 找最小值
int min(const std::vector<int>& arr);
// 求和
int sum(const std::vector<int>& arr);

#endif // ARRAY_UTILS_H
src/utils/array/array_utils.cpp
cpp 复制代码
#include "array/array_utils.h"
#include <algorithm>
#include <numeric>

int max(const std::vector<int>& arr) {
    if (arr.empty()) {
        throw std::invalid_argument("数组不能为空");
    }
    return *std::max_element(arr.begin(), arr.end());
}

int min(const std::vector<int>& arr) {
    if (arr.empty()) {
        throw std::invalid_argument("数组不能为空");
    }
    return *std::min_element(arr.begin(), arr.end());
}

int sum(const std::vector<int>& arr) {
    return std::accumulate(arr.begin(), arr.end(), 0);
}
src/utils/array/CMakeLists.txt
bash 复制代码
add_library(array_utils STATIC array_utils.cpp)
target_include_directories(array_utils PUBLIC ${CMAKE_SOURCE_DIR}/include)

2.6. src/utils/CMakeLists.txt(总控所有工具库)

bash 复制代码
# 按依赖顺序添加子工具库
add_subdirectory(math)
add_subdirectory(string)
add_subdirectory(array)

2.7. src/app/(主程序,简单演示工具用法)

src/app/main.cpp
cpp 复制代码
#include <iostream>
#include "math/calc.h"
#include "math/math_stats.h"
#include "string/string_utils.h"
#include "array/array_utils.h"

int main() {
    // 1. 演示计算器
    std::cout << "=== 计算器演示 ===" << std::endl;
    std::cout << "10 + 20 = " << add(10, 20) << std::endl;

    // 2. 演示字符串工具
    std::cout << "\n=== 字符串工具演示 ===" << std::endl;
    std::string s = "hello world";
    std::cout << "转大写: " << to_upper(s) << std::endl;

    // 3. 演示数组工具
    std::cout << "\n=== 数组工具演示 ===" << std::endl;
    std::vector<int> arr = {1, 3, 5, 2, 4};
    std::cout << "最大值: " << max(arr) << std::endl;

    return 0;
}
src/app/CMakeLists.txt
bash 复制代码
add_executable(toolkit_app main.cpp)
# 链接所有工具库
target_link_libraries(toolkit_app PRIVATE 
    math_calc 
    math_stats 
    string_utils 
    array_utils
)

3.重点:多模块测试代码编写

这是本次扩展的核心!我们为每个工具类单独写一个测试文件,覆盖正常、边界、异常、浮点数精度4 种场景。

3.1. tests/CMakeLists.txt(总控所有测试)

bash 复制代码
# 自动集成GoogleTest
include(FetchContent)
FetchContent_Declare(
    googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG v1.15.2
    GIT_SHALLOW TRUE
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

# 【新增】一个辅助函数:创建测试,避免重复代码
function(add_toolkit_test TEST_NAME TEST_SOURCE LINK_LIBS)
    add_executable(${TEST_NAME} ${TEST_SOURCE})
    target_link_libraries(${TEST_NAME} PRIVATE ${LINK_LIBS} GTest::gtest_main)
    include(GoogleTest)
    gtest_discover_tests(${TEST_NAME})
endfunction()

# 为每个工具类添加测试
add_toolkit_test(test_calc test_calc.cpp math_calc)
add_toolkit_test(test_math_stats test_math_stats.cpp math_stats)
add_toolkit_test(test_string_utils test_string_utils.cpp string_utils)
add_toolkit_test(test_array_utils test_array_utils.cpp array_utils)

3.2. 测试 1:基础计算器 tests/test_calc.cpp

cpp 复制代码
#include <gtest/gtest.h>
#include "math/calc.h"
#include <stdexcept>

// 正常场景
TEST(CalcTest, AddNormal) {
    EXPECT_EQ(add(1, 2), 3);
    EXPECT_EQ(add(-1, 1), 0);
    EXPECT_EQ(add(0, 0), 0);
}

// 边界场景
TEST(CalcTest, AddBoundary) {
    EXPECT_EQ(add(INT_MAX, 0), INT_MAX);
    EXPECT_EQ(add(INT_MIN, 0), INT_MIN);
}

// 异常场景
TEST(CalcTest, DivideZero) {
    EXPECT_THROW(divide(1, 0), std::invalid_argument);
    EXPECT_NO_THROW(divide(4, 2)); // 确保正常情况不抛异常
}

3.3. 测试 2:数学统计 tests/test_math_stats.cpp(重点看浮点数)

cpp 复制代码
#include <gtest/gtest.h>
#include "math/math_stats.h"
#include <stdexcept>
#include <vector>

// 辅助数据
const std::vector<double> TEST_DATA = {1.0, 2.0, 3.0, 4.0, 5.0};

// 正常场景 + 浮点数精度测试
TEST(MathStatsTest, MeanNormal) {
    // 【重点】浮点数不能用EXPECT_EQ,要用EXPECT_DOUBLE_EQ或EXPECT_NEAR
    EXPECT_DOUBLE_EQ(mean(TEST_DATA), 3.0);
}

TEST(MathStatsTest, VarianceNormal) {
    // 样本方差:( (1-3)^2 + ... + (5-3)^2 ) / 4 = 10 / 4 = 2.5
    EXPECT_DOUBLE_EQ(variance(TEST_DATA), 2.5);
}

TEST(MathStatsTest, StdDevNormal) {
    // 标准差:sqrt(2.5) ≈ 1.58113883008
    // 【重点】如果有精度误差,用EXPECT_NEAR指定误差范围
    EXPECT_NEAR(std_dev(TEST_DATA), 1.58113883008, 1e-9);
}

// 异常场景
TEST(MathStatsTest, EmptyData) {
    EXPECT_THROW(mean({}), std::invalid_argument);
    EXPECT_THROW(variance({1.0}), std::invalid_argument); // 只有1个数据
}

3.4. 测试 3:字符串工具 tests/test_string_utils.cpp

cpp 复制代码
#include <gtest/gtest.h>
#include "string/string_utils.h"
#include <vector>
#include <string>

// 正常场景
TEST(StringUtilsTest, ToUpperNormal) {
    EXPECT_EQ(to_upper("hello"), "HELLO");
    EXPECT_EQ(to_upper("Hello World"), "HELLO WORLD");
}

TEST(StringUtilsTest, StartsWithNormal) {
    EXPECT_TRUE(starts_with("hello world", "hello"));
    EXPECT_FALSE(starts_with("hello world", "world"));
}

TEST(StringUtilsTest, SplitNormal) {
    std::vector<std::string> expected = {"a", "b", "c"};
    EXPECT_EQ(split("a,b,c", ','), expected);
}

// 边界场景
TEST(StringUtilsTest, ToUpperEmpty) {
    EXPECT_EQ(to_upper(""), ""); // 空字符串
}

TEST(StringUtilsTest, StartsWithEmptyPrefix) {
    EXPECT_TRUE(starts_with("any", "")); // 空前缀
}

TEST(StringUtilsTest, SplitEmpty) {
    EXPECT_TRUE(split("", ',').empty()); // 空字符串分割
}

3.5. 测试 4:数组工具 tests/test_array_utils.cpp

cpp 复制代码
#include <gtest/gtest.h>
#include "array/array_utils.h"
#include <vector>
#include <stdexcept>

// 辅助数据
const std::vector<int> TEST_ARR = {3, 1, 4, 1, 5, 9};
const std::vector<int> SINGLE_ELEM_ARR = {42};

// 正常场景
TEST(ArrayUtilsTest, MaxNormal) {
    EXPECT_EQ(max(TEST_ARR), 9);
}

TEST(ArrayUtilsTest, MinNormal) {
    EXPECT_EQ(min(TEST_ARR), 1);
}

TEST(ArrayUtilsTest, SumNormal) {
    EXPECT_EQ(sum(TEST_ARR), 3+1+4+1+5+9);
}

// 边界场景
TEST(ArrayUtilsTest, SingleElement) {
    EXPECT_EQ(max(SINGLE_ELEM_ARR), 42);
    EXPECT_EQ(min(SINGLE_ELEM_ARR), 42);
    EXPECT_EQ(sum(SINGLE_ELEM_ARR), 42);
}

// 异常场景
TEST(ArrayUtilsTest, EmptyArray) {
    EXPECT_THROW(max({}), std::invalid_argument);
    EXPECT_THROW(min({}), std::invalid_argument);
    // sum空数组是允许的,返回0
    EXPECT_NO_THROW(sum({}));
    EXPECT_EQ(sum({}), 0);
}

4. 编译运行测试

4.1. 标准编译流程

bash 复制代码
# 1. 创建构建目录
mkdir build && cd build
# 2. 配置
cmake ..
# 3. 编译(会自动编译所有工具库、主程序、测试程序)
cmake --build . -j$(nproc)

4.2. 一键运行所有测试

bash 复制代码
ctest

你会看到类似这样的输出,所有测试用例都会被自动运行:

bash 复制代码
Test project /xxx/ToolKitProject/build
    Start 1: CalcTest.AddNormal
1/16 Test #1: CalcTest.AddNormal ................   Passed    0.00 sec
    Start 2: CalcTest.AddBoundary
2/16 Test #2: CalcTest.AddBoundary ..............   Passed    0.00 sec
...
    Start 16: ArrayUtilsTest.EmptyArray
16/16 Test #16: ArrayUtilsTest.EmptyArray ........   Passed    0.00 sec

100% tests passed, 0 tests failed out of 16

4.3. 运行单个测试程序(调试时用)

如果你只想看某一个工具的测试结果,可以直接运行对应的测试程序:

bash 复制代码
# 只运行数学统计的测试
./bin/test_math_stats

4.4. 主程序测试(端到端测试)

最后,你只需要手动运行主程序,当一次用户:

bash 复制代码
# 运行编译好的主程序
./bin/calc_app

完成主程序的测试,确认整个程序可以正常发布。


七、最后想说的话

测试从来都不是 "高手的专属技能",也不是 "大公司的繁文缛节",它是每个开发者都能用上的、最简单的 "代码保险"。

不用追求完美,不用一开始就写满所有测试用例 ------ 你可以先给项目里最核心的 1-2 个函数写 2 个测试用例,跑通一次,就能感受到它的好处。

毕竟,比起半夜爬起来修用户反馈的 "加法写成了减法" 的低级 bug,花 10 分钟写个测试,真的太值了。

相关推荐
小灰灰搞电子1 小时前
Qt UI 线程详解-阻塞与解决方案
开发语言·qt·ui
YYYing.1 小时前
【Linux/C++网络篇(二) 】TCP并发服务器演进史:从多进程到Epoll的进化指南
linux·服务器·网络·c++·tcp/ip
追光的蜗牛丿1 小时前
C++传递参数时什么情况下传递引用
开发语言·javascript·c++
Pocker_Spades_A1 小时前
Python快速入门专业版(五十六)——爬虫会话管理:Cookie与Session原理及实战(保持登录状态)
开发语言·爬虫·python
MwEUwQ3Gx1 小时前
深入理解 Java Deque 的设计哲学
java·开发语言·python
张人玉1 小时前
C#程序设计编程二维码识别程序
开发语言·c#·二维码
sheng42041 小时前
小记近期C++遇到的坑
c++
wregjru1 小时前
【高并发服务器项目】1.服务器接入层代码详解
c++
森G1 小时前
41、数据库---------事件系统
c++·qt