跳到主要内容
C++ 测试与调试:确保代码质量与稳定性 | 极客日志
C++ 算法
C++ 测试与调试:确保代码质量与稳定性 本文介绍了 C++ 测试与调试的核心知识,涵盖单元测试框架 Google Test 和 Catch2 的使用、GDB 及 Visual Studio 调试工具的操作、集成测试策略以及基于计算器的综合实战案例。通过规范化的项目结构和自动化构建流程,帮助开发者建立完善的代码质量保障体系,提升软件稳定性。
C++ 测试与调试:确保代码质量与稳定性
学习目标与重点
本章将深入探讨 C++ 测试与调试的核心知识,帮助你确保代码的质量与稳定性。通过实践,你将能够理解测试与调试的基本概念,掌握主流测试框架和工具的使用,并培养高质量的代码设计思维。
核心要点包括:
理解测试分类 :从单元测试到验收测试的完整体系
掌握测试框架 :熟练使用 Google Test 和 Catch2
集成测试实践 :确保模块间的协同工作
调试工具应用 :GDB 与 Visual Studio 调试器的实战技巧
测试的基本概念
测试的分类
测试不仅仅是找 Bug,它是保障软件可靠性的基石。常见的分类如下:
单元测试 :针对单个函数或类的功能验证
集成测试 :验证多个模块组合后的交互逻辑
系统测试 :对整个系统的功能进行端到端测试
验收测试 :确认系统是否满足用户需求
性能测试 :评估系统的响应速度与资源消耗
测试原则
好的测试应该遵循以下原则:
尽早介入 :在开发初期就引入测试
全面覆盖 :尽可能覆盖所有可能的场景
自动化执行 :减少人工重复劳动
可重复性 :测试结果应稳定一致
独立性 :测试用例之间互不依赖
单元测试框架
Google Test 框架
Google Test 是业界广泛使用的 C++ 单元测试框架,提供了丰富的断言和测试宏。
安装指南
sudo apt-get install libgtest-dev
brew install googletest
示例代码
下面是一个典型的 Google Test 结构,展示了如何测试 MyClass 的构造函数及方法:
#include <gtest/gtest.h>
#include "MyClass.h"
TEST (MyClassTest, ConstructorTest) {
MyClass obj;
(obj. (), );
}
(MyClassTest, SetGetValueTest) {
MyClass obj;
obj. ( );
(obj. (), );
}
(MyClassTest, AddTest) {
MyClass obj;
obj. ( );
result = obj. ( );
(result, );
(obj. (), );
}
(MyClassTest, SubtractTest) {
MyClass obj;
obj. ( );
result = obj. ( );
(result, );
(obj. (), );
}
{
::testing:: (&argc, argv);
();
}
EXPECT_EQ
getValue
0
TEST
setValue
42
EXPECT_EQ
getValue
42
TEST
setValue
10
int
add
20
EXPECT_EQ
30
EXPECT_EQ
getValue
30
TEST
setValue
50
int
subtract
20
EXPECT_EQ
30
EXPECT_EQ
getValue
30
int main (int argc, char ** argv)
InitGoogleTest
return
RUN_ALL_TESTS
Catch2 框架 Catch2 是另一个流行的选择,语法更简洁,功能同样强大。
sudo apt-get install catch2
brew install catch2
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
#include "MyClass.h"
TEST_CASE ("MyClass Constructor Test" , "[constructor]" ) {
MyClass obj;
REQUIRE (obj.getValue () == 0 );
}
TEST_CASE ("MyClass Set and Get Value Test" , "[setget]" ) {
MyClass obj;
obj.setValue (42 );
REQUIRE (obj.getValue () == 42 );
}
TEST_CASE ("MyClass Add Test" , "[add]" ) {
MyClass obj;
obj.setValue (10 );
int result = obj.add (20 );
REQUIRE (result == 30 );
REQUIRE (obj.getValue () == 30 );
}
TEST_CASE ("MyClass Subtract Test" , "[subtract]" ) {
MyClass obj;
obj.setValue (50 );
int result = obj.subtract (20 );
REQUIRE (result == 30 );
REQUIRE (obj.getValue () == 30 );
}
调试工具
GDB 调试器 GDB 是 Linux 下最经典的调试器,功能丰富且灵活。
g++ -g program.cpp -o program
gdb program
break main
run
next
step
print variable
watch variable
backtrace
continue
quit
#include <iostream>
#include "MyClass.h"
int main () {
MyClass obj;
obj.setValue (42 );
int result = obj.add (20 );
std::cout << "结果:" << result << std::endl;
return 0 ;
}
Visual Studio 调试器 Visual Studio 提供了强大的图形化界面,适合 Windows 环境下的开发。
在代码行号左侧点击设置断点
按 F5 启动调试
程序会在断点处暂停
使用调试窗口查看变量值和调用栈
根据需要继续执行程序
集成测试
基本概念 集成测试关注的是模块之间的协作。常见的集成策略有:
自顶向下 :从顶层模块开始,逐步集成底层
自底向上 :从底层模块开始,逐步集成顶层
三明治集成 :结合上述两种方法
示例 #include <gtest/gtest.h>
#include "Calculator.h"
#include "Parser.h"
TEST (IntegrationTest, CalculateTest) {
Calculator calculator;
Parser parser;
std::string expression = "10 + 20" ;
int result = parser.parse (expression);
EXPECT_EQ (result, 30 );
expression = "10 * 20" ;
result = parser.parse (expression);
EXPECT_EQ (result, 200 );
expression = "(10 + 20) * 3" ;
result = parser.parse (expression);
EXPECT_EQ (result, 90 );
}
综合案例:实现一个简单的计算器并进行测试
项目结构 CalculatorProject/
├── include/
│ ├── Calculator.h
│ └── Parser.h
├── src /
│ ├── Calculator.cpp
│ ├── Parser.cpp
│ └── main .cpp
├── tests/
│ ├── CalculatorTest.cpp
│ ├── ParserTest.cpp
│ └── IntegrationTest.cpp
└── CMakeLists.txt
核心代码 #ifndef CALCULATOR_H
#define CALCULATOR_H
class Calculator {
public :
static int add (int a, int b) ;
static int subtract (int a, int b) ;
static int multiply (int a, int b) ;
static int divide (int a, int b) ;
};
#endif
#include "Calculator.h"
#include <stdexcept>
int Calculator::add (int a, int b) {
return a + b;
}
int Calculator::subtract (int a, int b) {
return a - b;
}
int Calculator::multiply (int a, int b) {
return a * b;
}
int Calculator::divide (int a, int b) {
if (b == 0 ) {
throw std::invalid_argument ("除数不能为零" );
}
return a / b;
}
#ifndef PARSER_H
#define PARSER_H
#include <string>
#include "Calculator.h"
class Parser {
public :
int parse (const std::string& expression) ;
};
#endif
#include "Parser.h"
#include <sstream>
int Parser::parse (const std::string& expression) {
std::istringstream iss (expression) ;
int a, b;
char op;
iss >> a >> op >> b;
switch (op) {
case '+' : return Calculator::add (a, b);
case '-' : return Calculator::subtract (a, b);
case '*' : return Calculator::multiply (a, b);
case '/' : return Calculator::divide (a, b);
default : throw std::invalid_argument ("无效的运算符" );
}
}
#include <iostream>
#include "Parser.h"
int main () {
Parser parser;
try {
std::string expression;
std::cout << "请输入表达式(例如:10 + 20): " ;
std::getline (std::cin, expression);
int result = parser.parse (expression);
std::cout << "结果:" << result << std::endl;
} catch (const std::exception& e) {
std::cerr << "错误:" << e.what () << std::endl;
return 1 ;
}
return 0 ;
}
#include <gtest/gtest.h>
#include "Calculator.h"
TEST (CalculatorTest, AddTest) {
EXPECT_EQ (Calculator::add (10 , 20 ), 30 );
EXPECT_EQ (Calculator::add (-10 , 20 ), 10 );
EXPECT_EQ (Calculator::add (0 , 0 ), 0 );
}
TEST (CalculatorTest, SubtractTest) {
EXPECT_EQ (Calculator::subtract (20 , 10 ), 10 );
EXPECT_EQ (Calculator::subtract (10 , 20 ), -10 );
EXPECT_EQ (Calculator::subtract (0 , 0 ), 0 );
}
TEST (CalculatorTest, MultiplyTest) {
EXPECT_EQ (Calculator::multiply (10 , 20 ), 200 );
EXPECT_EQ (Calculator::multiply (-10 , 20 ), -200 );
EXPECT_EQ (Calculator::multiply (0 , 10 ), 0 );
}
TEST (CalculatorTest, DivideTest) {
EXPECT_EQ (Calculator::divide (20 , 10 ), 2 );
EXPECT_EQ (Calculator::divide (-20 , 10 ), -2 );
EXPECT_THROW (Calculator::divide (10 , 0 ), std::invalid_argument);
}
#include <gtest/gtest.h>
#include "Parser.h"
TEST (ParserTest, ParseTest) {
Parser parser;
EXPECT_EQ (parser.parse ("10 + 20" ), 30 );
EXPECT_EQ (parser.parse ("20 - 10" ), 10 );
EXPECT_EQ (parser.parse ("10 * 20" ), 200 );
EXPECT_EQ (parser.parse ("20 / 10" ), 2 );
EXPECT_THROW (parser.parse ("10 / 0" ), std::invalid_argument);
EXPECT_THROW (parser.parse ("10 a 20" ), std::invalid_argument);
}
#include <gtest/gtest.h>
#include "Parser.h"
TEST (IntegrationTest, CalculateTest) {
Parser parser;
EXPECT_EQ (parser.parse ("10 + 20" ), 30 );
EXPECT_EQ (parser.parse ("20 - 10" ), 10 );
EXPECT_EQ (parser.parse ("10 * 20" ), 200 );
EXPECT_EQ (parser.parse ("20 / 10" ), 2 );
EXPECT_THROW (parser.parse ("10 / 0" ), std::invalid_argument);
}
cmake_minimum_required(VERSION 3.10)
project(CalculatorProject)
set(CMAKE_CXX_STANDARD 17)
include_directories(include)
add_library(Calculator src/Calculator.cpp)
add_library(Parser src/Parser.cpp)
add_executable(CalculatorApp src/main.cpp)
target_link_libraries(CalculatorApp Calculator Parser)
add_subdirectory(tests)
cmake_minimum_required(VERSION 3.10)
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})
add_executable(CalculatorTests CalculatorTest.cpp ParserTest.cpp IntegrationTest.cpp)
target_link_libraries(CalculatorTests ${GTEST_LIBRARIES} Calculator Parser pthread)
include(GoogleTest)
gtest_discover_tests(CalculatorTests)
构建与测试
mkdir -p build && cd build
cmake ..
make
ctest
总结与练习 本章涵盖了 C++ 测试与调试的核心内容,从基础概念到实战案例。掌握了这些技能后,你的代码可靠性将大幅提升。
使用 Google Test 测试一个简单的链表数据结构
编写函数并使用 GDB 调试内存泄漏问题
利用 Visual Studio 调试器排查多线程程序问题
用 Catch2 测试自定义类的功能
为银行账户系统设计集成测试方案
研究 Google Mock 进行模拟对象测试
学习 Google Benchmark 进行性能分析
使用 gcov 获取代码覆盖率报告
结合 Clang Static Analyzer 进行静态检查
通过 GitHub Actions 实现持续集成自动化测试
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online