cpp-unit-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUnit Testing
单元测试
Instructions for AI coding agents on automating unit test creation using consistent software testing patterns in this C++ project.
AI编码代理在C++项目中使用一致软件测试模式自动化创建单元测试的操作指南。
1. Benefits
1. 优势
-
ReadabilityEnsures high code quality and reliability. Tests are self-documenting, reducing cognitive load for reviewers and maintainers.
-
ConsistencyUniform structure across tests ensures predictable, familiar code that team members can navigate efficiently.
-
ScalabilityTable-driven and data-driven approaches minimize boilerplate code when adding new test cases, making it simple to expand coverage.
-
DebuggabilityScoped traces and detailed assertion messages pinpoint failures quickly during continuous integration and local testing.
-
可读性确保高代码质量和可靠性。测试具备自文档性,降低评审人员和维护人员的认知负担。
-
一致性所有测试采用统一结构,形成可预测、易熟悉的代码,团队成员可高效浏览。
-
可扩展性表格驱动和数据驱动方法在添加新测试用例时最大程度减少样板代码,轻松扩展测试覆盖率。
-
可调试性范围跟踪和详细断言信息可在持续集成和本地测试期间快速定位故障。
2. Principles
2. 原则
2.1. FIRST
2.1. FIRST
The principles for unit testing focus on creating effective and maintainable tests.
FIRST-
FastUnit tests should execute quickly to provide rapid feedback during development and continuous integration.
-
IndependentEach unit test should be self-contained and not rely on the state or behavior of other tests.
-
RepeatableUnit tests should produce deterministic results every time they are run, regardless of the environment or order of execution.
-
Self-ValidatingUnit tests should have clear pass/fail outcomes without requiring manual inspection.
-
TimelyUnit tests should be written and executed early in the development process to catch issues as soon as possible.
单元测试的原则专注于创建高效且可维护的测试。
FIRST-
快速(Fast)单元测试应快速执行,以便在开发和持续集成过程中提供快速反馈。
-
独立(Independent)每个单元测试应独立存在,不依赖其他测试的状态或行为。
-
可重复(Repeatable)无论执行环境或顺序如何,单元测试每次运行都应产生确定的结果。
-
自验证(Self-Validating)单元测试应具备明确的通过/失败结果,无需人工检查。
-
及时(Timely)单元测试应在开发流程早期编写和执行,以便尽早发现问题。
3. Patterns
3. 模式
3.1. In-Got-Want
3.1. In-Got-Want
The In-Got-Want pattern structures each test case into three clear sections.
-
InDefines the input parameters or conditions for the test.
-
GotCaptures the actual output or result produced by the code under test.
-
WantSpecifies the expected output or result that the test is verifying against.
In-Got-Want模式将每个测试用例划分为三个清晰的部分。
-
In(输入)定义测试的输入参数或条件。
-
Got(实际输出)捕获被测代码产生的实际输出或结果。
-
Want(预期输出)指定测试要验证的预期输出或结果。
3.2. Table-Driven Testing
3.2. 表格驱动测试
Table-driven testing organizes test cases in a tabular format, allowing multiple scenarios to be defined concisely.
-
Test Case StructureEach row in the table represents a distinct test case with its own set of inputs and expected outputs.
-
IterationThe test framework iterates over each row, executing the same test logic with different data.
表格驱动测试以表格形式组织测试用例,可简洁定义多个场景。
-
测试用例结构表格中的每一行代表一个独立的测试用例,包含自身的输入和预期输出。
-
迭代执行测试框架遍历每一行,使用不同数据执行相同的测试逻辑。
3.3. Data-Driven Testing (DDT)
3.3. 数据驱动测试(DDT)
Data-driven testing separates test data from test logic, enabling the same test logic to be executed with multiple sets of input data.
-
External Data SourcesTest data can be stored in external files (e.g., JSON, CSV) and loaded at runtime.
-
ReusabilityThe same test logic can be reused with different datasets, enhancing maintainability and coverage.
数据驱动测试将测试数据与测试逻辑分离,使相同的测试逻辑可与多组输入数据一起执行。
-
外部数据源测试数据可存储在外部文件(如JSON、CSV)中,并在运行时加载。
-
可复用性相同的测试逻辑可与不同数据集复用,提升可维护性和测试覆盖率。
3.4. Arrange, Act, Assert (AAA)
3.4. 准备-执行-断言(AAA)
The AAA pattern structures each test case into three clear phases.
-
ArrangeSet up the necessary preconditions and inputs for the test.
-
ActExecute the function or method being tested.
-
AssertVerify that the actual output matches the expected output.
AAA模式将每个测试用例划分为三个清晰的阶段。
-
准备(Arrange)设置测试所需的必要前置条件和输入。
-
执行(Act)执行被测函数或方法。
-
断言(Assert)验证实际输出是否与预期输出匹配。
3.5. Test Fixtures
3.5. 测试夹具
Test fixtures provide a consistent and reusable setup and teardown mechanism for test cases.
-
SetupInitialize common objects or state needed for multiple tests.
-
TeardownClean up resources or reset state after each test.
测试夹具为测试用例提供一致且可复用的初始化和清理机制。
-
初始化(Setup)初始化多个测试所需的通用对象或状态。
-
清理(Teardown)在每个测试后清理资源或重置状态。
3.6. Test Doubles
3.6. 测试替身
Test doubles (e.g., mocks, stubs, fakes) are simplified versions of complex objects or components used to isolate the unit under test.
-
MocksSimulate the behavior of real objects and verify interactions.
-
StubsProvide predefined responses to method calls without implementing full behavior.
-
FakesImplement simplified versions of real objects with limited functionality.
测试替身(如模拟对象、桩对象、伪对象)是复杂对象或组件的简化版本,用于隔离被测单元。
-
模拟对象(Mocks)模拟真实对象的行为并验证交互。
-
桩对象(Stubs)为方法调用提供预定义响应,无需实现完整行为。
-
伪对象(Fakes)实现真实对象的简化版本,仅具备有限功能。
4. Workflow
4. 工作流程
-
IdentifyIdentify new functions in(e.g.,
src/).src/<module>/<header>.hpp -
Add/CreateCreate new tests colocated with source code in(e.g.,
src/<module>/).src/<module>/<header>_test.cpp -
Register with CMakeAdd the test file tousing
src/<module>/CMakeLists.txtwith appropriate options (e.g.,meta_gtest()).WITH_DDTThe test configuration should useoption withENABLEvariable:META_BUILD_TESTINGcmakeinclude(meta_gtest) meta_gtest( ENABLE ${META_BUILD_TESTING} TARGET ${PROJECT_NAME}-test SOURCES <header>_test.cpp LINK ${PROJECT_NAME}::<module> ) -
Test Coverage RequirementsInclude comprehensive edge cases:
- Coverage-guided cases
- Boundary values (min/max limits, edge thresholds)
- Empty/null inputs
- Null pointers and invalid references
- Overflow/underflow scenarios
- Special cases (negative numbers, zero, special states)
-
Apply TemplatesStructure all tests using the template pattern below.
-
识别识别中的新函数(如
src/)。src/<module>/<header>.hpp -
添加/创建在源代码所在目录中创建新测试文件(如
src/<module>/)。src/<module>/<header>_test.cpp -
向CMake注册使用并添加适当选项(如
meta_gtest()),将测试文件添加到WITH_DDT中。src/<module>/CMakeLists.txt测试配置应使用选项并结合ENABLE变量:META_BUILD_TESTINGcmakeinclude(meta_gtest) meta_gtest( ENABLE ${META_BUILD_TESTING} TARGET ${PROJECT_NAME}-test SOURCES <header>_test.cpp LINK ${PROJECT_NAME}::<module> ) -
测试覆盖率要求需涵盖全面的边缘场景:
- 覆盖率引导的测试用例
- 边界值(最小/最大限制、边缘阈值)
- 空/空值输入
- 空指针和无效引用
- 溢出/下溢场景
- 特殊情况(负数、零、特殊状态)
-
应用模板使用以下模板结构编写所有测试。
5. Commands
5. 命令
| Command | Description |
|---|---|
| CMake preset configuration and Compile with Ninja |
| Execute tests via ctest |
| Execute tests via ctest and generate coverage reports |
| 命令 | 描述 |
|---|---|
| CMake预设配置并使用Ninja编译 |
| 通过ctest执行测试 |
| 通过ctest执行测试并生成覆盖率报告 |
6. Style Guide
6. 风格指南
-
Test FrameworkUse GoogleTest (GTest) framework via.
#include <gtest/gtest.h> -
Include HeadersInclude necessary standard library headers (,
<vector>,<string>, etc.) and module-specific headers in a logical order: system headers first, then project headers.<climits>Include necessary headers in this order:- GTest/GMock headers (,
<gtest/gtest.h>)<gmock/gmock.h> - Standard library headers (,
<memory>, etc.)<string> - Project interface headers
- Project implementation headers
- GTest/GMock headers (
-
NamespaceUsefor convenience within test functions to reduce verbosity while maintaining clarity, since test scope is limited.
using namespace <namespace>; -
Test OrganizationConsolidate test cases for a single function into onefunction using table-driven testing.
TEST(...)This approach:- Eliminates redundant test function definitions
- Simplifies maintenance by grouping related scenarios together
- Reduces code duplication in setup and teardown phases
- Makes it easier to add or modify test cases
-
Testing MacrosFocus eachfunction on a single function or cohesive behavior. For complex setups, use
TEST(...)fixtures or helper functions to reduce duplication.TEST_F -
MockingUse Google Mock (GMock) for creating test doubles (mocks, stubs, fakes) to isolate the unit under test. See the cpp-mock-testing skill.
-
TraceabilityEmployfor traceable failures in table-driven tests.
SCOPED_TRACE(tc.label) -
AssertionsUsemacros (not
EXPECT_*) to allow all test cases to run.ASSERT_*
-
测试框架通过使用GoogleTest (GTest)框架。
#include <gtest/gtest.h> -
头文件包含按逻辑顺序包含必要的标准库头文件(、
<vector>、<string>等)和模块特定头文件:先包含系统头文件,再包含项目头文件。<climits>按以下顺序包含必要的头文件:- GTest/GMock头文件(、
<gtest/gtest.h>)<gmock/gmock.h> - 标准库头文件(、
<memory>等)<string> - 项目接口头文件
- 项目实现头文件
- GTest/GMock头文件(
-
命名空间在测试函数中使用以简化代码,同时保持清晰性,因为测试范围有限。
using namespace <namespace>; -
测试组织使用表格驱动测试,将单个函数的所有测试用例整合到一个函数中。
TEST(...)这种方法:- 消除冗余的测试函数定义
- 通过分组相关场景简化维护
- 减少初始化和清理阶段的代码重复
- 更轻松地添加或修改测试用例
-
测试宏每个函数专注于单个函数或内聚行为。对于复杂的初始化,使用
TEST(...)夹具或辅助函数减少重复代码。TEST_F -
模拟使用Google Mock (GMock)创建测试替身(模拟对象、桩对象、伪对象)以隔离被测单元。请参阅cpp-mock-testing技能文档。
-
可追溯性在表格驱动测试中使用实现可追溯的故障定位。
SCOPED_TRACE(tc.label) -
断言使用宏(而非
EXPECT_*)以允许所有测试用例执行完毕。ASSERT_*
7. Template
7. 模板
Use these templates for new unit tests. Replace placeholders with actual values.
使用以下模板创建新的单元测试。将占位符替换为实际值。
7.1. File Header Template
7.1. 文件头模板
cpp
#include <gtest/gtest.h>
#include <string>
#include <vector>
#include "<module>/<header>.hpp"
using namespace <namespace>;cpp
#include <gtest/gtest.h>
#include <string>
#include <vector>
#include "<module>/<header>.hpp"
using namespace <namespace>;7.2. Table-Driven Test Template
7.2. 表格驱动测试模板
cpp
TEST(<Module>Test, <FunctionName>)
{
// In-Got-Want
struct Tests
{
std::string label;
struct In
{
/* input types and names */
} in;
struct Want
{
/* expected output type(s) and name(s) */
} want;
};
// Table-Driven Testing
const std::vector<Tests> tests = {
{"case-description-1", {/* input */}, {/* expected */}},
{"case-description-2", {/* input */}, {/* expected */}},
};
for (const auto &tc : tests)
{
SCOPED_TRACE(tc.label);
// Arrange
<Module> <object>;
// Act
auto got = <object>.<function>(tc.in.<input>);
// Assert
EXPECT_EQ(got, tc.want.<expected>);
}
}cpp
TEST(<Module>Test, <FunctionName>)
{
// In-Got-Want
struct Tests
{
std::string label;
struct In
{
/* input types and names */
} in;
struct Want
{
/* expected output type(s) and name(s) */
} want;
};
// Table-Driven Testing
const std::vector<Tests> tests = {
{"case-description-1", {/* input */}, {/* expected */}},
{"case-description-2", {/* input */}, {/* expected */}},
};
for (const auto &tc : tests)
{
SCOPED_TRACE(tc.label);
// Arrange
<Module> <object>;
// Act
auto got = <object>.<function>(tc.in.<input>);
// Assert
EXPECT_EQ(got, tc.want.<expected>);
}
}7.3. Test Fixture Template
7.3. 测试夹具模板
cpp
class <Module>Test : public ::testing::Test
{
protected:
void SetUp() override
{
// Initialize common objects or state
}
void TearDown() override
{
// Clean up resources or reset state
}
<Module> object_;
};
TEST_F(<Module>Test, <FunctionName>)
{
// Arrange
auto input = <input_value>;
// Act
auto got = object_.<function>(input);
// Assert
EXPECT_EQ(got, <expected>);
}cpp
class <Module>Test : public ::testing::Test
{
protected:
void SetUp() override
{
// Initialize common objects or state
}
void TearDown() override
{
// Clean up resources or reset state
}
<Module> object_;
};
TEST_F(<Module>Test, <FunctionName>)
{
// Arrange
auto input = <input_value>;
// Act
auto got = object_.<function>(input);
// Assert
EXPECT_EQ(got, <expected>);
}7.4. Exception Test Template
7.4. 异常测试模板
cpp
TEST(<Module>Test, <FunctionName>ThrowsOnInvalidInput)
{
// Arrange
<Module> object;
auto invalid_input = <invalid_value>;
// Act & Assert
EXPECT_THROW(object.<function>(invalid_input), <ExceptionType>);
}cpp
TEST(<Module>Test, <FunctionName>ThrowsOnInvalidInput)
{
// Arrange
<Module> object;
auto invalid_input = <invalid_value>;
// Act & Assert
EXPECT_THROW(object.<function>(invalid_input), <ExceptionType>);
}7.5. Boundary Value Test Template
7.5. 边界值测试模板
cpp
TEST(<Module>Test, <FunctionName>BoundaryValues)
{
// In-Got-Want
struct Tests
{
std::string label;
struct In
{
<input_type> input;
} in;
struct Want
{
<output_type> expected;
} want;
};
// Table-Driven Testing with boundary cases
const std::vector<Tests> tests = {
{"minimum-value", {<MIN_VALUE>}, {/* expected */}},
{"maximum-value", {<MAX_VALUE>}, {/* expected */}},
{"zero-value", {0}, {/* expected */}},
{"empty-input", {{}}, {/* expected */}},
{"negative-value", {-1}, {/* expected */}},
};
for (const auto &tc : tests)
{
SCOPED_TRACE(tc.label);
// Arrange
<Module> object;
// Act
auto got = object.<function>(tc.in.input);
// Assert
EXPECT_EQ(got, tc.want.expected);
}
}cpp
TEST(<Module>Test, <FunctionName>BoundaryValues)
{
// In-Got-Want
struct Tests
{
std::string label;
struct In
{
<input_type> input;
} in;
struct Want
{
<output_type> expected;
} want;
};
// Table-Driven Testing with boundary cases
const std::vector<Tests> tests = {
{"minimum-value", {<MIN_VALUE>}, {/* expected */}},
{"maximum-value", {<MAX_VALUE>}, {/* expected */}},
{"zero-value", {0}, {/* expected */}},
{"empty-input", {{}}, {/* expected */}},
{"negative-value", {-1}, {/* expected */}},
};
for (const auto &tc : tests)
{
SCOPED_TRACE(tc.label);
// Arrange
<Module> object;
// Act
auto got = object.<function>(tc.in.input);
// Assert
EXPECT_EQ(got, tc.want.expected);
}
}7.6. Data-Driven Test Template (JSON)
7.6. 数据驱动测试模板(JSON)
cpp
#include <nlohmann/json.hpp>
#include <fstream>
TEST(<Module>Test, <FunctionName>DataDriven)
{
// Load test data from JSON file
std::ifstream file("<module>/<header>_test.json");
nlohmann::json test_data;
file >> test_data;
for (const auto &tc : test_data["tests"])
{
SCOPED_TRACE(tc["label"].get<std::string>());
// Arrange
<Module> object;
auto input = tc["in"]["input"].get<<input_type>>();
auto expected = tc["want"]["expected"].get<<output_type>>();
// Act
auto got = object.<function>(input);
// Assert
EXPECT_EQ(got, expected);
}
}cpp
#include <nlohmann/json.hpp>
#include <fstream>
TEST(<Module>Test, <FunctionName>DataDriven)
{
// Load test data from JSON file
std::ifstream file("<module>/<header>_test.json");
nlohmann::json test_data;
file >> test_data;
for (const auto &tc : test_data["tests"])
{
SCOPED_TRACE(tc["label"].get<std::string>());
// Arrange
<Module> object;
auto input = tc["in"]["input"].get<<input_type>>();
auto expected = tc["want"]["expected"].get<<output_type>>();
// Act
auto got = object.<function>(input);
// Assert
EXPECT_EQ(got, expected);
}
}