C++测试:框架与工具
高质量测试是C++开发中不可或缺的一部分。与动态语言不同,C++需要特别关注内存管理、对象生命周期和性能。本文将介绍三种最流行的单元测试框架:Google Test、Catch2和doctest,并涉及一些辅助工具。
为什么C++需要测试?
C++是一种手动管理内存并拥有强大模板机制的语言。没有测试,很容易出现:
- 内存泄漏和重复释放
- 指针操作中的未定义行为 (UB)
- 运算符重载和拷贝构造函数中的错误
- 重构遗留代码时的回归问题
测试框架可以自动化验证过程,帮助快速定位问题。
Google Test (gtest)
来自Google的最流行框架。支持所有主要功能:ASSERT_*和EXPECT_*宏、参数化测试、测试夹具 (test fixtures)、死亡测试。
安装(通过CMake FetchContent)
include(FetchContent)FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.14.0)FetchContent_MakeAvailable(googletest)简单测试示例
#include <gtest/gtest.h>
int factorial(int n) { return (n <= 1) ? 1 : n * factorial(n - 1);}
TEST(FactorialTest, HandlesZeroInput) { EXPECT_EQ(factorial(0), 1);}
TEST(FactorialTest, HandlesPositiveInput) { EXPECT_EQ(factorial(5), 120); EXPECT_EQ(factorial(3), 6);}
int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();}测试夹具和参数化测试
class MathFixture : public ::testing::Test {protected: void SetUp() override { /* 初始化 */ } void TearDown() override { /* 清理 */ }};
// 参数化测试class SumTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {};TEST_P(SumTest, CheckSum) { auto [a, b, expected] = GetParam(); EXPECT_EQ(a + b, expected);}INSTANTIATE_TEST_SUITE_P(SumParams, SumTest, ::testing::Values( std::make_tuple(1, 1, 2), std::make_tuple(0, 0, 0) ));优点:庞大的社区、与CI/CD集成、强大的宏、支持死亡测试。
缺点:编译相对较慢、可执行文件体积较大。
Catch2
现代化的框架,注重测试的可读性。采用BDD风格(行为驱动开发)并支持节 (sections)。
安装(header-only)
Catch2以单个头文件形式分发。只需下载catch_amalgamated.hpp并包含它即可。
使用节的示例
#define CATCH_CONFIG_MAIN#include <catch2/catch.hpp>
int divide(int a, int b) { if (b == 0) throw std::invalid_argument("division by zero"); return a / b;}
TEST_CASE("Division tests", "[math]") { SECTION("Normal division") { REQUIRE(divide(10, 2) == 5); } SECTION("Division by zero") { REQUIRE_THROWS_AS(divide(5, 0), std::invalid_argument); }}BDD风格(Given-When-Then)
SCENARIO("Vector push_back operation", "[container]") { GIVEN("An empty vector") { std::vector<int> v; WHEN("We push one element") { v.push_back(42); THEN("The size becomes 1") { REQUIRE(v.size() == 1); } AND_THEN("The element is correct") { REQUIRE(v[0] == 42);