引言:为什么测试对 C++ 项目至关重要
测试 C++ 代码不仅仅是一种形式,更是一种确保软件稳定性、安全性和可维护性的基本实践。与解释型语言不同,在解释型语言中错误可能立即显现,而在 C++ 中,bug 通常只有在编译或特定条件下执行时才会暴露。被忽略的内存泄漏、未定义行为或错误的指针处理都可能导致致命崩溃。
现代软件开发行业区分了三个主要的测试层级:单元测试、集成测试和端到端测试。每个层级解决其自身的任务,它们共同构成了“测试金字塔”,能够在发布到生产环境前捕获多达 90% 的错误。
在本文中,我们将使用流行的框架 Google Test 和 Catch2,通过 C++ 的实际示例来详细讲解每种测试类型,并讨论其最佳实践。
1. 单元测试:检查原子模块
单元测试是最底层、最快的测试层级。它们独立于系统其余部分,检查单个函数、类或方法。目标是确保每个代码的“构件块”都能正确工作。
使用 Google Test 的单元测试示例
考虑一个简单的数学函数:
// math_utils.h#pragma once
namespace MathUtils { int add(int a, int b); int divide(int a, int b);}// math_utils.cpp#include "math_utils.h"
namespace MathUtils { int add(int a, int b) { return a + b; }
int divide(int a, int b) { if (b == 0) { throw std::invalid_argument("Division by zero"); } return a / b; }}现在让我们使用 Google Test 编写单元测试:
// test_math_utils.cpp#include <gtest/gtest.h>#include "math_utils.h"
TEST(MathUtilsTest, AddPositiveNumbers) { EXPECT_EQ(MathUtils::add(2, 3), 5); EXPECT_EQ(MathUtils::add(0, 0), 0);}
TEST(MathUtilsTest, AddNegativeNumbers) { EXPECT_EQ(MathUtils::add(-1, -1), -2); EXPECT_EQ(MathUtils::add(-5, 3), -2);}
TEST(MathUtilsTest, DivideNormal) { EXPECT_EQ(MathUtils::divide(10, 2), 5); EXPECT_EQ(MathUtils::divide(7, 3), 2); // Integer division}
TEST(MathUtilsTest, DivideByZeroThrows) { EXPECT_THROW(MathUtils::divide(5, 0), std::invalid_argument);}使用模拟对象
为了隔离模块,通常需要替换依赖项。Google Mock(Google Test 的一部分)允许创建桩件:
// database_interface.hclass DatabaseInterface {public: virtual ~DatabaseInterface() = default; virtual bool save(const std::string& data) = 0;};
// user_service.cppclass UserService { DatabaseInterface& db;public: explicit UserService(DatabaseInterface& db) : db(db) {} bool registerUser(const std::string& name) { return db.save("user:" + name); }};
// test_user_service.cpp#include <gmock/gmock.h>
class MockDatabase : public DatabaseInterface {public: MOCK_METHOD(bool, save, (const std::string&), (override));};
TEST(UserServiceTest, RegisterUserCallsDb) { MockDatabase mockDb; EXPECT_CALL(mockDb, save("user:Alice")).WillOnce(testing::Return(true));
UserService service(mockDb); EXPECT_TRUE(service.registerUser("Alice"));}优点: 执行速度快(毫秒级),错误定位精确,无需外部依赖即可运行。
缺点: 不检查真实组件之间的交互。
2. 集成测试:检查组件连接
集成测试检查多个模