C++ 测试驱动开发
什么是测试驱动开发?
测试驱动开发(Test-Driven Development,简称TDD)是一种软件开发方法,它强调在编写实际功能代码之前先编写测试代码。这种方法颠覆了传统的开发流程,使开发人员能够更加专注于需求和设计,同时也能降低软件缺陷率。
TDD的基本流程可以概括为"红-绿-重构"(Red-Green-Refactor)循环:
- 红:编写一个会失败的测试
- 绿:编写最少的代码使测试通过
- 重构:改进代码,确保它结构良好且高效
为什么选择TDD?
- 确保代码有测试覆盖
- 引导开发者思考设计和需求
- 增加对代码变更的信心
- 提供即时反馈
- 促进简单设计
C++ 中的TDD工具
在C++中实施TDD,你需要掌握一些测试框架。以下是最常用的几种:
- Google Test:谷歌开发的C++测试框架,支持断言、参数化测试等
- Catch2:轻量级、易于使用的测试框架,只需包含头文件
- Boost.Test:Boost库提供的测试工具
- CppUnit:C++版本的JUnit
- DocTest:类似Catch但编译更快的框架
我们将在本教程中使用Google Test作为示例,因为它功能全面且广泛使用。
安装配置Google Test
首先,我们需要安装Google Test框架。以下是基本步骤:
使用CMake安装
// CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyTDDProject)
# Google Test需要线程支持
find_package(Threads REQUIRED)
# 下载并构建GoogleTest
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.11.0
)
FetchContent_MakeAvailable(googletest)
# 启用测试
enable_testing()
# 添加我们的可执行文件
add_executable(
my_tests
test.cpp
)
# 链接GoogleTest和线程库
target_link_libraries(
my_tests
gtest_main
Threads::Threads
)
# 将测试添加到ctest
include(GoogleTest)
gtest_discover_tests(my_tests)
第一个TDD示例:实现一个简单计算器
让我们通过实现一个简单的计算器来演示TDD的基本流程。
步骤1:编写第一个失败的测试
首先,我们定义测试文件calculator_test.cpp
:
#include <gtest/gtest.h>
// 我们还没有实现Calculator类
#include "calculator.h"
// 测试加法功能
TEST(CalculatorTest, Addition) {
Calculator calc;
EXPECT_EQ(5, calc.Add(2, 3));
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
运行这段代码时,测试将失败,因为我们还没有实现Calculator
类。
步骤2:创建最简实现让测试通过
接下来,我们创建calculator.h
:
#ifndef CALCULATOR_H
#define CALCULATOR_H
class Calculator {
public:
int Add(int a, int b) {
return a + b;
}
};
#endif // CALCULATOR_H
现在,我们的测试应该能通过了。这是"绿"阶段。
步骤3:添加更多测试
让我们为其他操作添加测试:
// 测试减法功能
TEST(CalculatorTest, Subtraction) {
Calculator calc;
EXPECT_EQ(5, calc.Subtract(10, 5));
}
// 测试乘法功能
TEST(CalculatorTest, Multiplication) {
Calculator calc;
EXPECT_EQ(15, calc.Multiply(3, 5));
}
// 测试除法功能
TEST(CalculatorTest, Division) {
Calculator calc;
EXPECT_EQ(3, calc.Divide(15, 5));
// 测试除以0的情况
EXPECT_THROW(calc.Divide(10, 0), std::invalid_argument);
}
这些测试将再次失败,因为我们还没有实现这些方法。
步骤4:实现功能以通过测试
更新calculator.h
:
#ifndef CALCULATOR_H
#define CALCULATOR_H
#include <stdexcept>
class Calculator {
public:
int Add(int a, int b) {
return a + b;
}
int Subtract(int a, int b) {
return a - b;
}
int Multiply(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;
}
};
#endif // CALCULATOR_H
现在所有测试应该都能通过了。
步骤5:重构
在这个简单的例子中,代码已经很清晰了,但在实际项目中,你可能需要重构代码,例如:
- 提取共同逻辑
- 优化性能
- 改进命名
- 移除重复代码
重构后,确保所有测试仍然通过。