在 Ubuntu/Debian 系统上使用 GoogleTest 框架进行单元测试时,libgtest-dev 是必备的开发包。与大多数预编译库不同,这个包只提供源代码,需要开发者手动编译生成静态库文件。这种设计虽然增加了初始配置的复杂度,但带来了更好的灵活性和可控性。
首先通过 apt 安装基础包:
bash复制sudo apt update
sudo apt install libgtest-dev cmake make g++
安装完成后,关键文件分布在两个目录:
/usr/include/gtest/usr/src/gtest注意:如果遇到 "unable to locate package libgtest-dev" 错误,请先确保已启用 universe 仓库:
sudo add-apt-repository universe && sudo apt update
bash复制cd /usr/src/gtest
sudo cmake CMakeLists.txt
sudo make
sudo cp *.a /usr/lib
bash复制cd /usr/src/gtest
sudo cmake --build . --target install
bash复制cd /usr/src/gtest
sudo g++ -I. -c *.cc
sudo ar -rc libgtest.a *.o
sudo cp libgtest.a /usr/lib
编译完成后会生成两个关键静态库:
对于生产环境测试,推荐添加以下编译选项:
bash复制cmake -DCMAKE_CXX_FLAGS="-O2 -march=native -fPIC" ..
参数说明:
-O2:优化级别平衡代码大小和执行速度-march=native:针对当前 CPU 架构优化-fPIC:生成位置无关代码,便于动态链接现代 C++ 项目通常使用 CMake 作为构建系统,GoogleTest 提供了完善的 CMake 支持。以下是三种集成方案及其适用场景。
cmake复制find_package(GTest REQUIRED)
add_executable(my_tests test.cpp)
target_link_libraries(my_tests PRIVATE GTest::gtest GTest::gtest_main)
include(GoogleTest)
gtest_discover_tests(my_tests)
优势:
cmake复制add_executable(my_tests test.cpp)
target_include_directories(my_tests PRIVATE /usr/include/gtest)
target_link_libraries(my_tests PRIVATE gtest gtest_main pthread)
适用场景:
cmake复制include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.13.0.zip
)
FetchContent_MakeAvailable(googletest)
add_executable(my_tests test.cpp)
target_link_libraries(my_tests PRIVATE gtest_main)
优势:
GoogleTest 提供丰富的断言宏,可分为两大类:
| 类型 | 特点 | 适用场景 |
|---|---|---|
| EXPECT_* | 测试继续执行 | 验证非关键条件 |
| ASSERT_* | 立即终止测试 | 验证关键前提条件 |
常用断言示例:
cpp复制// 相等性验证
EXPECT_EQ(Calculate(2,3), 5);
// 异常验证
ASSERT_THROW(Parse("invalid"), std::exception);
// 浮点数比较
EXPECT_NEAR(sqrt(2), 1.414, 0.001);
// 容器内容检查
std::vector<int> v = {1,2,3};
EXPECT_THAT(v, Contains(2));
测试夹具(Test Fixture)通过继承 ::testing::Test 实现,典型结构:
cpp复制class DatabaseTest : public ::testing::Test {
protected:
void SetUp() override {
db = new Database(":memory:");
db->initialize();
}
void TearDown() override {
db->close();
delete db;
}
Database* db;
};
TEST_F(DatabaseTest, InsertRecord) {
EXPECT_TRUE(db->insert("key", "value"));
EXPECT_EQ(db->size(), 1);
}
生命周期说明:
通过 TEST_P 宏实现数据驱动测试:
cpp复制class PrimeTest : public testing::TestWithParam<int> {};
TEST_P(PrimeTest, IsPrime) {
int n = GetParam();
EXPECT_TRUE(IsPrime(n));
}
INSTANTIATE_TEST_SUITE_P(
PrimeValues,
PrimeTest,
testing::Values(2, 3, 5, 7, 11, 13, 17, 19)
);
高级参数生成方式:
testing::Range(start, end, step):数值范围testing::ValuesIn(container):从容器取值testing::Combine():参数组合验证程序在错误条件下的崩溃行为:
cpp复制TEST(DeathTest, InvalidInput) {
EXPECT_DEATH({
ProcessFile("/invalid/path");
}, "File not found");
}
配置选项:
::testing::FLAGS_gtest_death_test_style = "threadsafe":线程安全模式EXPECT_DEATH_IF_SUPPORTED:跨平台兼容版本使用 GoogleMock 创建模拟对象:
cpp复制class MockSensor : public SensorInterface {
public:
MOCK_METHOD(float, GetTemperature, (), (override));
};
TEST(ACTest, Overheating) {
MockSensor sensor;
EXPECT_CALL(sensor, GetTemperature())
.WillOnce(Return(100.0f));
AirConditioner ac(&sensor);
EXPECT_TRUE(ac.IsOverheating());
}
常用匹配器:
Return(value):返回固定值Invoke(func):调用函数SetArgReferee<N>(value):修改引用参数在 CMake 中启用覆盖率收集:
cmake复制option(ENABLE_COVERAGE "Enable coverage reporting" OFF)
if(ENABLE_COVERAGE)
target_compile_options(my_tests PRIVATE --coverage)
target_link_libraries(my_tests PRIVATE --coverage)
add_custom_target(coverage
COMMAND lcov --capture --directory . --output-file coverage.info
COMMAND genhtml coverage.info --output-directory coverage_report
DEPENDS my_tests
)
endif()
关键步骤:
--coverage 选项bash复制./my_tests --gtest_shuffle --gtest_repeat=3 --gtest_break_on_failure
bash复制# 只运行特定测试套件
./my_tests --gtest_filter=DatabaseTest.*
# 排除性能测试
./my_tests --gtest_filter=-*.PerformanceTest
cpp复制class HeavyFixture : public ::testing::Test {
public:
static void SetUpTestSuite() {
shared_resource = new Resource();
}
static void TearDownTestSuite() {
delete shared_resource;
}
static Resource* shared_resource;
};
推荐目录结构:
code复制project/
├── src/
│ └── ...
└── tests/
├── unit/
│ ├── math_test.cpp
│ └── db_test.cpp
├── integration/
│ └── api_test.cpp
└── CMakeLists.txt
测试文件命名约定:
模块名_test.cpp:基本单元测试模块名_integration_test.cpp:集成测试模块名_perf_test.cpp:性能测试问题1:链接错误 "undefined reference to testing::InitGoogleTest"
问题2:测试失败但无详细输出
cpp复制testing::InitGoogleTest(&argc, argv);
testing::FLAGS_gtest_verbose = 1;
return RUN_ALL_TESTS();
问题3:内存泄漏检测
cpp复制TEST(MemoryTest, LeakCheck) {
void* p = malloc(100);
EXPECT_NE(p, nullptr);
free(p); // 注释掉这行会触发失败
}
在实际项目中,我通常会建立一个基础的测试模板项目,包含:
这样新项目可以快速继承成熟的测试基础设施,避免重复配置。对于大型项目,建议将测试代码与生产代码同等重视,纳入代码评审和质量门禁。