为什么测试很重要?
测试是软件开发过程中从开始到结束的关键步骤。 测试是通过分析软件程序运行情况来识别程序中可能存在的缺陷或不一致性的过程。 测试对于确保软件按预期运行并为终端用户提供良好体验至关重要。 它还有助于保持软件应用的可靠性和健壮性,这是一个额外优势。
单元测试是测试流程的初始阶段。 作为软件测试的子集,单元测试负责检验和验证软件中独立组件与模块的功能性。 该流程的目标是确保软件的每个组件都能按预期工作。 在 C++语境中,"单元测试"特指对单个函数或类的测试。 其核心目标是验证给定特定参数集时,每个函数和类的行为是否符合预期。 这包括处理异常情况和错误状态。C++的单元测试可通过多种现有框架实现,例如 Google Test 和 Catch2。 这些框架提供了一系列集成工具和功能,能显著简化单元测试的编写与管理流程。
下一阶段的测试被称为集成测试。 此类测试旨在发现被测集成组件间交互过程中存在的缺陷。 换言之,集成测试着重分析软件各组件之间的交互行为。 这在后端环境中尤为重要,因为构成系统的各类服务与组件之间需要频繁通信。 可以使用 Postman 或 Insomnia 等软件测试 REST API 的集成点。 这将确保 API 在组合使用时能按预期运行。
端到端测试(End-to-End testing,简称 E2E 测试)是一种验证应用程序流程是否从头到尾按预期运行的方法论。 进行端到端测试旨在确定系统中哪些部分相互依赖,并验证数据完整性是否在所有不同的系统组件和系统间得以保持。 这种测试关注应用程序完整的功能流程,而无需特别关注系统表面之下的逻辑。 在后端开发中,测试 API 完整的请求和响应周期就是端到端测试的典型示例。 这包括验证 API 能否对各种输入提供恰当响应、能否正确处理错误,以及在不同条件下是否保持良好的性能。
最终需要强调的是,测试是软件开发生命周期中不可或缺的组成部分。 它有助于确保软件应用程序的健壮性和可靠性,并为用户提供良好的使用体验。 从单个组件的测试到不同模块间交互点的测试,再到完整功能流程的测试,每个测试层级都提供独特的价值,共同提升软件应用的整体质量。 单元测试、集成测试和端到端测试就是这类测试的典型示例。 因此,必须投入时间和资源来为正在开发的软件项目制定全面的测试策略并构建相应的测试基础设施。
C++测试框架:Doctest 与 Google Test
Doctest 和 Google Test 是 C++开发环境中广泛使用的测试框架。 它们为编写、管理和运行测试提供了强大而灵活的平台。 这些框架支持多种断言来验证代码功能,帮助开发者确保代码的正确性和可靠性。
Doctest
Doctest 是一个相对较新的 C++测试框架,其灵感来源于 Python 的 unittest{}功能及 doctest Python 模块。 它极其轻量,据称是功能丰富且最轻量级的 C++测试框架。 它既可用于编写测试用例,也支持一种可执行文档的形式。
Doctest 的主要优势包括:
● 轻量级:相比其他测试框架,Doctest 增加的编译时间极少。 这使得它特别适合包含大量测试的大型项目。
● 易用性:Doctest 允许直接在 C++代码中编写测试,无需任何额外的源文件。 这使得配置和使用更加简单。
● 灵活性:Doctest 支持多种测试场景,如参数化测试、子测试和模板测试用例,并允许在测试和代码段中进行日志记录。
● 集成性:Doctest 提供了与系统日志模块的集成,因此您可以使用相同的语法编写测试和记录日志消息。
Google Test (gtest)
Google Test(又称 gtest)是最流行的 C++ 测试框架之一。 它由 Google 开发,被 Google 内外数千个项目所采用。
Google Test 的主要优势包括:
● 丰富功能:Google Test 支持从简单测试到复杂功能测试的广泛需求。 它提供了处理比较的断言,其高级特性允许您控制测试的运行方式。
● 可移植性:Google Test 可在多种平台上运行,且在不同平台间迁移时无需修改代码。
● 灵活性:Google Test 支持自动测试发现、运行测试的高级选项,以及创建复杂测试层次结构的能力。
● 健壮性:Google Test 能够处理异常,并设计为与 Google C++ Mocking Framework 良好协作。
Doctest 和 Google Test 都是 C++测试的绝佳选择。 但最佳选择取决于您的具体需求。Google Test 作为更成熟且功能丰富的框架,非常适合复杂应用程序。 相比之下,Doctest 轻量级且编译迅速,对于编译时间是关键因素的应用更为合适。
安装 Google Test
以下是将其安装到开发环境中的步骤。
图 9.1 安装 Google Test 的流程
克隆 Google Test 代码库
首先,您需要将 Google Test 代码库克隆到本地机器。 可以通过在终端执行以下 git 命令实现:
git clone https://github.com/google/googletest.git
构建 Google Test 库
克隆代码库后,需要构建 Google Test 库。Google Test 使用 CMake 进行构建,这是一个用于管理软件构建的常见开源系统。 请进入克隆代码库时创建的 googletest 目录:
cd googletest
然后,新建一个用于存放构建文件的目录并进入该目录:
mkdir build
cd build
然后运行 CMake 命令来构建项目。 这将为您创建所需的 Makefile:
cmake ..
运行 cmake 命令后,您可以使用 make 构建项目:
make
安装 Google Test
构建库文件后,您可以将其安装到系统中。 可以使用 make install 命令完成安装:
sudo make install
将 Google Test 链接到您的项目
安装 Google Test 库后,需要将其链接到您的 C++项目中。 您可以通过在 CMakeLists.txt 文件中添加必要的包含目录和链接库来实现:
include_directories(/usr/local/include)
link_directories(/usr/local/lib)
…
add_executable(your_test your_test.cpp)
target_link_libraries(your_test gtest gtest_main)
在上述代码片段中,your_test.cpp 应替换为您的测试文件名。gtest 和 gtest_main 是 Google Test 库。
执行数据丢失的单元测试
让我们创建一个简单的 Google Test 单元测试,用于验证后端应用中一个假设的数据保存功能。 在以下示例用例中,假设我们有一个 DataStorage 类,其中包含保存数据的 SaveData 函数和检索数据的 GetData 函数。
图9.2 单元测试流程
包含必要的库和项目头文件
首先,你需要在测试文件中包含必要的库和项目头文件。
#include <gtest/gtest.h>
#include "data_storage.h" // This should be the path to your DataStorage class
编写测试
接下来,您可以编写测试本身。 下面是一个检查数据丢失的测试示例:
TEST(DataStorageTest, CheckDataLoss) {
// Initialize the data storage
DataStorage ds;
// Set up some test data
std::string testData = "Test Data";
// Save the data
ds.SaveData(testData);
// Retrieve the data
std::string retrievedData = ds.GetData();
// Check if the retrieved data is the same as the saved data
EXPECT_EQ(testData, retrievedData);
}
在本测试中,我们首先初始化 DataStorage 对象。 然后设置一些测试数据并使用 SaveData 函数保存。 通过 GetData 获取数据后,使用 EXPECT_EQ 宏验证检索到的数据是否与保存的数据一致。
运行测试
您可以使用 CMake 和 Make 编译并运行测试。 以下是一个示例操作方式:
mkdir build
cd build
cmake ..
make
./run_tests
在上述代码片段中,run_tests 是由您的 CMakeLists.txt 文件创建的可执行文件,用于运行测试。
这是一个非常简化的示例程序,但您可以根据需要扩展它。 例如,您可能拥有比简单字符串更复杂的数据,或者需要为不同功能或使用场景编写多个测试。 关键在于您需要经常且全面地进行测试,以确保代码行为符合预期。
对 MongoDB 和 gRPC 进行集成测试
集成测试旨在验证系统中不同组件能否按预期协同工作。 当系统包含 gRPC 服务器和 MongoDB 数据库时,集成测试可验证向 gRPC 服务器发出的请求能否在数据库中正确执行相应操作。 以下示例展示了如何使用 Google Test 框架构建此类测试。 本测试将聚焦博客应用的 CRUD 操作。
图 9.3 MongoDB 与 gRPC 间的集成测试
搭建测试环境
首先你需要一个测试环境,这可以是一个专门用于测试的独立数据库,或者是一个内存数据库。
#include <gtest/gtest.h>
#include <grpcpp/grpcpp.h>
#include "blog.grpc.pb.h" // the generated protobuf file
class IntegrationTest : public ::testing::Test {
protected:
void SetUp() override {
// Setup goes here.
// This could include connecting to your MongoDB database.
// Connecting to your gRPC server.
}
void TearDown() override {
// Cleanup goes here.
// This could include disconnecting from your MongoDB database and gRPC server.
}
};
实现测试
现在您可以实现测试来验证 gRPC 服务器和 MongoDB 数据库是否能按预期协同工作。
TEST_F(IntegrationTest, CreatePost) {
// Create a new post request
blog::CreatePostRequest request;
blog::Post* post = request.mutable_post();
post->set_title("Test Title");
post->set_content("Test Content");
// Make the gRPC call
grpc::ClientContext context;
blog::CreatePostResponse response;
grpc::Status status = stub_->CreatePost(&context, request, &response);
// Check the status of the gRPC call
ASSERT_TRUE(status.ok());
// Check that the post was created in the database
// This will depend on how your MongoDB is set up.
// You could query the MongoDB database to check if the new post exists.
}
// You could implement similar tests for updating, deleting, and reading posts.
运行测试
一旦您的 CMakeLists.txt 文件配置完成,就可以使用 CMake 生成构建文件。 通常在一个单独的构建目录中进行此操作,以保持项目结构清晰。 命令'cmake ..' 用于生成这些文件,其中'..' 表示 CMake 应在父目录中查找 CMakeLists.txt 文件。 生成构建文件后,您可以使用 Make 编译代码并构建项目。'make'命令将构建 CMakeLists.txt 文件中指定的所有目标。 如果 CMakeLists.txt 文件配置正确,这应包括构建您的测试。 要运行测试,通常使用类似'./run_tests'的命令,其中'run_tests'是 CMakeLists.txt 文件创建的测试可执行文件名称。 这将运行所有测试并将结果输出到控制台。
这是一个简化示例,具体细节可能因项目配置而异。 例如,您的项目可能具有不同的依赖项,或者源代码文件组织方式不同。 但整体流程——编写测试、使用 CMake 和 Make 构建项目、运行测试——应该是相似的。
持续测试与测试自动化
持续测试是现代软件开发流程中不可或缺的组成部分。 其目标是尽早测试、频繁测试,以便能够尽快发现并解决问题。 它通常与持续集成(CI)和持续部署(CD)紧密结合,共同构成 CI/CD 流水线。
持续测试需要将测试过程自动化,使得每次代码库发生变更时都能自动运行测试。 这可以在错误部署到生产环境之前将其捕获,确保应用程序始终处于可部署状态。
设置持续测试的示例程序
让我们以 GitHub Actions 为例,它提供了一种直接从 GitHub 自动化软件工作流程(包括测试)的方法。 以下是通过 GitHub Actions 为 C++项目设置持续测试的逐步指南:
创建新工作流
在代码仓库中,于.github/workflows 目录下创建新文件来定义工作流。 可将其命名为 test.yml。
定义工作流程
工作流使用 YAML 语法定义。 从高层次来看,您需要指定工作流的触发条件、运行环境以及执行步骤。
一个基础的工作流示例如下:
name: C++ CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
– uses: actions/checkout@v2
– name: install dependencies
run: sudo apt-get install libgtest-dev cmake -y
– name: build
run: cmake .
– name: test
run: make && ./test_app # or however you run your tests
此工作流程在每次推送提交或针对主分支打开拉取请求时触发。它在 Ubuntu 环境中运行。
步骤如下:
● 从当前代码库检出代码。
● 安装必要的依赖项(Google Test 和 CMake)。
● 使用 CMake 构建应用程序。
● 使用 make 命令和测试可执行文件运行测试。
定义此工作流程后,每次提交推送到 master 分支或针对该分支打开拉取请求时,测试将自动运行。 如果测试失败,您将收到通知,并可查看日志了解问题所在。 请记住,持续测试的主要目标是尽早发现问题,确保应用程序始终处于可部署状态。 通过此设置,您可以确信测试将始终运行,问题会在进入生产环境前被发现。
故障排除与解决方案
让我们探讨在 C++环境中(特别是使用 Google Test 和持续测试配置时)常见的一些问题及其解决方案。
Google Test 编译问题
在尝试编译 Google Test 或测试代码时可能会遇到错误。 请确认已安装所有必要的依赖项并包含了正确的头文件。 如果使用的是较旧版本的 C++,请确保其与当前 Google Test 版本兼容。 务必仔细查看错误信息详情,它们通常能提供有价值的线索。
测试意外失败
如果预期通过的测试失败了,首先检查错误信息和测试输出。 尝试通过运行部分测试或简化测试代码来隔离问题。 确保已正确设置必要的测试数据或模拟对象。
不稳定测试
有时测试结果可能不一致,时而通过时而失败。 这可能是由于竞态条件、对系统状态的依赖或随机数生成等非确定性因素导致的。 尽量使测试具有确定性,并隔离无法控制的因素。 必要时可使用模拟对象或固定随机数种子。
设置持续测试时遇到的麻烦
如果在使用 GitHub Actions 设置持续测试环境时遇到问题,请先查阅 GitHub Actions 文档并参考示例工作流。 确保您的工作流文件位置正确且语法无误。 您可以通过 GitHub 仓库的 Actions 选项卡查看工作流运行结果及错误信息。
GitHub Actions 无法定位文件/目录
请注意 GitHub Actions 会在特定目录(/github/workspace)中检出您的代码。 如果在测试或工作流中引用文件或目录,请确保路径正确。
GitHub Actions 密钥问题
如果您在工作流中使用密钥(例如用于部署),请确保已在仓库设置中正确配置它们,并在工作流中正确引用。 切勿在工作流文件中硬编码敏感信息!
总结
本章重点探讨了测试在后端开发中的重要作用以及测试可采取的各种形式。 单元测试、集成测试和端到端测试这三种类型各自在确保单个软件组件、组件间交互以及整体系统功能按设计运行方面发挥着重要作用。 测试不仅能确保代码正确性,还有助于防止错误再次发生、便于重构,并起到文档记录的作用。 对 Google Test 和 Doctest 等 C++测试框架的介绍,揭示了这些工具为 C++应用程序带来的易用性和诸多优势。
我们深入探讨了在 C++环境中安装和使用 Google Test 的具体细节,包括编写测试以防止数据丢失的过程,以及与 MongoDB 和 gRPC 进行集成测试的方法。 通过实际实现测试用例并运行的过程,我们理解了使用 Google Test 更实用的方面,并亲身体验了该工具的健壮性和多功能性。 我们完成了为博客应用程序搭建集成测试环境的各个步骤。 在整个过程中,我们强调了在尽可能接近生产环境的情况下进行测试的重要性。
在本章最后一节中,我们探讨了持续测试的概念及其配置流程。 持续测试能确保每次代码变更推送至代码库时都进行验证,从而及早发现问题并构建健壮的开发流水线。 我们研究了最常见的故障排除场景,针对测试编译问题、意外测试失败、不稳定测试以及持续测试环境搭建难题提供了实用解决方案。 此外还详细讲解了问题排查与解决的流程,重点强调了这些技能在实际软件开发领域中的重要性。
评论前必须登录!
注册