当前位置: 首页 > news >正文

网站开发 项目的人员分配建筑工程网络计划编制软件

网站开发 项目的人员分配,建筑工程网络计划编制软件,linux wordpress 升级,wordpress 固定链接 seo智能图书馆管理系统开发实战系列(六):Google Test单元测试实践 前言 在前面的文章中,我们完成了前后端集成,系统的核心功能已经实现。但是一个高质量的软件项目离不开完善的测试体系。本文将详细介绍如何使用Google …

智能图书馆管理系统开发实战系列(六):Google Test单元测试实践

前言

在前面的文章中,我们完成了前后端集成,系统的核心功能已经实现。但是一个高质量的软件项目离不开完善的测试体系。本文将详细介绍如何使用Google Test框架为我们的C++后端代码编写全面的单元测试,以及如何在CI/CD流程中集成自动化测试。

Google Test框架概述

为什么选择Google Test?

Google Test(简称gtest)是Google开发的C++单元测试框架,具有以下优势:

  1. 功能完善: 提供丰富的断言宏和测试功能
  2. 易于使用: 简洁的API设计,学习成本低
  3. 跨平台: 支持Windows、Linux、macOS等多平台
  4. 集成友好: 易于与CMake、CI/CD等工具集成
  5. 社区活跃: 广泛使用,文档完善

测试项目架构

code/backend/gtester/ 目录结构可以看到我们的测试组织方式:

code/backend/gtester/
├── CMakeLists.txt              # CMake测试配置
├── readme.txt                 # 测试说明文档
├── Src/                        # 测试源代码
│   ├── main.cpp               # 测试主入口
│   └── TestFramework/         # 测试框架组织
│       ├── BookManager/       # 图书管理测试
│       ├── ReaderManager/     # 读者管理测试
│       ├── LoanManager/       # 借阅管理测试
│       ├── Dashboard/         # 仪表板测试
│       ├── QuerySearch/       # 查询搜索测试
│       ├── StatisticsReport/  # 统计报表测试
│       ├── SystemSettings/    # 系统设置测试
│       ├── LibGlobal/         # 全局功能测试
│       ├── HelloWorldTest.cpp # 基础测试示例
│       └── TestBase.h         # 测试基础类

测试组织特点:

  • 模块对应: 测试结构与业务代码结构一一对应
  • 功能细分: 每个具体功能都有独立的测试模块
  • 层次清晰: 从全局测试到具体功能测试的层次结构

CMake测试配置

基础配置解析

code/backend/gtester/CMakeLists.txt 的配置可以看到测试项目的构建设置:

cmake_minimum_required(VERSION 3.10)
project (GTestRunner)# Unicode配置支持
if(MSVC)add_definitions(-DUNICODE -D_UNICODE)# 启用UTF-8源码编码add_compile_options(/utf-8)# 设置字符集为Unicodeset(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHa")
endif()# 非MSVC编译器配置
if(NOT MSVC)add_definitions(-DUNICODE -D_UNICODE)add_compile_options(-finput-charset=UTF-8 -fexec-charset=UTF-8)
endif()# 环境变量配置
SET(UNISDK_ROOT_PROJ "$ENV{UNISDK_ROOT}")
cmake_policy(SET CMP0053 NEW)# 构建输出优化
set(CMAKE_SUPPRESS_REGENERATION true)
set_directory_properties(PROPERTIESADDITIONAL_MAKE_CLEAN_FILES "${CMAKE_BINARY_DIR}/ZERO_CHECK"
)

配置亮点:

  • 跨平台支持: 同时支持MSVC和GCC/Clang编译器
  • Unicode支持: 完整的Unicode字符支持
  • 异常处理: 配置C++异常处理机制
  • 构建优化: 优化构建输出,减少无用文件

Google Test集成配置

# 查找Google Test
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})# 包含被测试的业务代码头文件
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../dll/Src)
include_directories(${UNISDK_ROOT_PROJ}/export_csdk/)
include_directories(${UNISDK_ROOT_PROJ}/export_cppsdk/)# 组织测试源文件
file(GLOB TESTFRAMEWORK_GROUP_FILES"${CMAKE_CURRENT_SOURCE_DIR}/Src/TestFramework/*.cpp""${CMAKE_CURRENT_SOURCE_DIR}/Src/TestFramework/*.h"
)file(GLOB TESTFRAMEWORK_BOOKMANAGER_GROUP_FILES"${CMAKE_CURRENT_SOURCE_DIR}/Src/TestFramework/BookManager/*.cpp""${CMAKE_CURRENT_SOURCE_DIR}/Src/TestFramework/BookManager/*.h"
)# 创建测试可执行文件
add_executable(GTestRunner${TESTFRAMEWORK_GROUP_FILES}${TESTFRAMEWORK_BOOKMANAGER_GROUP_FILES}${TESTFRAMEWORK_LOANMANAGER_GROUP_FILES}${TESTFRAMEWORK_DASHBOARD_GROUP_FILES}
)# 链接Google Test库和业务代码库
target_link_libraries(GTestRunner${GTEST_LIBRARIES}${GTEST_MAIN_LIBRARIES}libBackend  # 链接我们的业务逻辑DLLpthread     # Linux下需要线程库
)# 启用测试
enable_testing()
add_test(NAME LibrarySystemTests COMMAND GTestRunner)

核心测试模块实现

1. 图书管理模块测试

添加图书功能测试
// Src/TestFramework/BookManager/AddBook/AddBookTest.cpp
#include <gtest/gtest.h>
#include <json/json.h>
#include "Impl/BookManager/BookManager.h"
#include "TestFramework/TestHelper.h"using namespace LibrarySystem::BookManager;class AddBookTest : public ::testing::Test {
protected:void SetUp() override {// 测试前准备:初始化测试环境TestHelper::InitializeTestDatabase();TestHelper::ClearBookData();}void TearDown() override {// 测试后清理:清理测试数据TestHelper::ClearBookData();TestHelper::CleanupTestDatabase();}// 创建有效的图书数据Json::Value CreateValidBookData() {Json::Value book;book["title"] = "Test Book Title";book["author"] = "Test Author";book["isbn"] = "9787111213826";book["category"] = "Technology";book["publisher"] = "Test Publisher";book["publishDate"] = "2024-01-01";book["description"] = "Test Description";return book;}// 创建无效的图书数据Json::Value CreateInvalidBookData(const std::string& invalidField) {Json::Value book = CreateValidBookData();if (invalidField == "title") {book["title"] = "";} else if (invalidField == "isbn") {book["isbn"] = "invalid-isbn";} else if (invalidField == "missing_required") {book.removeMember("title");}return book;}
};// 测试成功添加图书
TEST_F(AddBookTest, AddValidBook_Success) {// ArrangeJson::Value bookData = CreateValidBookData();std::string bookJson = TestHelper::JsonToString(bookData);std::string resultJson;// Actbool result = BookManagerImpl::AddBook(bookJson, resultJson);// AssertEXPECT_TRUE(result);EXPECT_FALSE(resultJson.empty());Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_TRUE(resultData["success"].asBool());EXPECT_TRUE(resultData.isMember("bookId"));EXPECT_FALSE(resultData["bookId"].asString().empty());// 验证图书是否真的被添加到数据库std::string bookId = resultData["bookId"].asString();EXPECT_TRUE(TestHelper::IsBookExistsInDatabase(bookId));
}// 测试添加重复ISBN的图书
TEST_F(AddBookTest, AddDuplicateISBN_Failure) {// ArrangeJson::Value bookData = CreateValidBookData();std::string bookJson = TestHelper::JsonToString(bookData);std::string resultJson1, resultJson2;// 先添加一本书bool firstResult = BookManagerImpl::AddBook(bookJson, resultJson1);ASSERT_TRUE(firstResult);// Act: 尝试添加相同ISBN的书bool secondResult = BookManagerImpl::AddBook(bookJson, resultJson2);// AssertEXPECT_FALSE(secondResult);Json::Value resultData = TestHelper::StringToJson(resultJson2);EXPECT_FALSE(resultData["success"].asBool());EXPECT_EQ(resultData["error"].asString(), "Book already exists");
}// 测试添加无效数据的图书
TEST_F(AddBookTest, AddInvalidBook_Failure) {// 测试空标题{Json::Value invalidBook = CreateInvalidBookData("title");std::string bookJson = TestHelper::JsonToString(invalidBook);std::string resultJson;bool result = BookManagerImpl::AddBook(bookJson, resultJson);EXPECT_FALSE(result);Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_FALSE(resultData["success"].asBool());EXPECT_EQ(resultData["error"].asString(), "Invalid book data");}// 测试无效ISBN{Json::Value invalidBook = CreateInvalidBookData("isbn");std::string bookJson = TestHelper::JsonToString(invalidBook);std::string resultJson;bool result = BookManagerImpl::AddBook(bookJson, resultJson);EXPECT_FALSE(result);Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_FALSE(resultData["success"].asBool());}
}// 测试JSON格式错误
TEST_F(AddBookTest, AddInvalidJSON_Failure) {// Arrangestd::string invalidJson = "{ invalid json format }";std::string resultJson;// Actbool result = BookManagerImpl::AddBook(invalidJson, resultJson);// AssertEXPECT_FALSE(result);Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_FALSE(resultData["success"].asBool());EXPECT_EQ(resultData["error"].asString(), "Invalid JSON format");
}// 性能测试:批量添加图书
TEST_F(AddBookTest, BatchAddBooks_Performance) {const int bookCount = 1000;std::vector<std::string> bookIds;auto startTime = std::chrono::high_resolution_clock::now();for (int i = 0; i < bookCount; ++i) {Json::Value bookData = CreateValidBookData();bookData["title"] = "Test Book " + std::to_string(i);bookData["isbn"] = TestHelper::GenerateISBN(i);std::string bookJson = TestHelper::JsonToString(bookData);std::string resultJson;bool result = BookManagerImpl::AddBook(bookJson, resultJson);ASSERT_TRUE(result);Json::Value resultData = TestHelper::StringToJson(resultJson);bookIds.push_back(resultData["bookId"].asString());}auto endTime = std::chrono::high_resolution_clock::now();auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);// 性能要求:1000本书添加应该在5秒内完成EXPECT_LT(duration.count(), 5000);// 验证所有图书都被正确添加EXPECT_EQ(bookIds.size(), bookCount);for (const auto& bookId : bookIds) {EXPECT_TRUE(TestHelper::IsBookExistsInDatabase(bookId));}std::cout << "Added " << bookCount << " books in " << duration.count() << "ms" << std::endl;
}
删除图书功能测试
// Src/TestFramework/BookManager/DeleteBook/DeleteBookTest.cpp
#include <gtest/gtest.h>
#include "Impl/BookManager/BookManager.h"
#include "TestFramework/TestHelper.h"class DeleteBookTest : public ::testing::Test {
protected:void SetUp() override {TestHelper::InitializeTestDatabase();TestHelper::ClearBookData();// 预先添加一些测试图书testBookId = TestHelper::AddTestBook("Test Book for Deletion");borrowedBookId = TestHelper::AddTestBook("Borrowed Book");TestHelper::BorrowBookInTest(borrowedBookId, "test_user_001");}void TearDown() override {TestHelper::ClearBookData();TestHelper::CleanupTestDatabase();}std::string testBookId;std::string borrowedBookId;
};// 测试删除可用图书
TEST_F(DeleteBookTest, DeleteAvailableBook_Success) {// ArrangeASSERT_TRUE(TestHelper::IsBookExistsInDatabase(testBookId));std::string resultJson;// Actbool result = BookManagerImpl::DeleteBook(testBookId, resultJson);// AssertEXPECT_TRUE(result);Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_TRUE(resultData["success"].asBool());// 验证图书已从数据库中删除EXPECT_FALSE(TestHelper::IsBookExistsInDatabase(testBookId));
}// 测试删除不存在的图书
TEST_F(DeleteBookTest, DeleteNonExistentBook_Failure) {// Arrangestd::string nonExistentId = "non_existent_book_id";std::string resultJson;// Actbool result = BookManagerImpl::DeleteBook(nonExistentId, resultJson);// AssertEXPECT_FALSE(result);Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_FALSE(resultData["success"].asBool());EXPECT_EQ(resultData["error"].asString(), "Book not found");
}// 测试删除已借出的图书
TEST_F(DeleteBookTest, DeleteBorrowedBook_Failure) {// ArrangeASSERT_TRUE(TestHelper::IsBookBorrowed(borrowedBookId));std::string resultJson;// Actbool result = BookManagerImpl::DeleteBook(borrowedBookId, resultJson);// AssertEXPECT_FALSE(result);Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_FALSE(resultData["success"].asBool());EXPECT_EQ(resultData["error"].asString(), "Cannot delete borrowed book");// 验证图书仍然存在EXPECT_TRUE(TestHelper::IsBookExistsInDatabase(borrowedBookId));
}

2. 借阅管理模块测试

// Src/TestFramework/LoanManager/LoanManagerTest.cpp
#include <gtest/gtest.h>
#include "Impl/LoanManager/LoanManager.h"
#include "TestFramework/TestHelper.h"using namespace LibrarySystem::LoanManager;class LoanManagerTest : public ::testing::Test {
protected:void SetUp() override {TestHelper::InitializeTestDatabase();TestHelper::ClearAllData();// 创建测试数据availableBookId = TestHelper::AddTestBook("Available Book");borrowedBookId = TestHelper::AddTestBook("Borrowed Book");validUserId = TestHelper::AddTestUser("Test User", "test@example.com");blockedUserId = TestHelper::AddTestUser("Blocked User", "blocked@example.com");// 设置已借出的图书TestHelper::BorrowBookInTest(borrowedBookId, validUserId);// 设置被封禁的用户TestHelper::BlockUserInTest(blockedUserId);}void TearDown() override {TestHelper::ClearAllData();TestHelper::CleanupTestDatabase();}std::string availableBookId;std::string borrowedBookId;std::string validUserId;std::string blockedUserId;
};// 测试正常借书流程
TEST_F(LoanManagerTest, BorrowAvailableBook_Success) {// ArrangeJson::Value request;request["bookId"] = availableBookId;request["userId"] = validUserId;std::string requestJson = TestHelper::JsonToString(request);std::string resultJson;// Actbool result = LoanManagerImpl::BorrowBook(requestJson, resultJson);// AssertEXPECT_TRUE(result);Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_TRUE(resultData["success"].asBool());EXPECT_TRUE(resultData.isMember("borrowId"));EXPECT_TRUE(resultData.isMember("dueDate"));// 验证借阅记录已创建std::string borrowId = resultData["borrowId"].asString();EXPECT_TRUE(TestHelper::IsBorrowRecordExists(borrowId));// 验证图书状态已更新为已借出EXPECT_TRUE(TestHelper::IsBookBorrowed(availableBookId));
}// 测试借已被借出的图书
TEST_F(LoanManagerTest, BorrowUnavailableBook_Failure) {// ArrangeJson::Value request;request["bookId"] = borrowedBookId;request["userId"] = validUserId;std::string requestJson = TestHelper::JsonToString(request);std::string resultJson;// Actbool result = LoanManagerImpl::BorrowBook(requestJson, resultJson);// AssertEXPECT_FALSE(result);Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_FALSE(resultData["success"].asBool());EXPECT_EQ(resultData["error"].asString(), "Book not available");
}// 测试被封禁用户借书
TEST_F(LoanManagerTest, BorrowBookWithBlockedUser_Failure) {// ArrangeJson::Value request;request["bookId"] = availableBookId;request["userId"] = blockedUserId;std::string requestJson = TestHelper::JsonToString(request);std::string resultJson;// Actbool result = LoanManagerImpl::BorrowBook(requestJson, resultJson);// AssertEXPECT_FALSE(result);Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_FALSE(resultData["success"].asBool());EXPECT_EQ(resultData["error"].asString(), "User borrow limit exceeded");
}// 测试归还图书
TEST_F(LoanManagerTest, ReturnBorrowedBook_Success) {// Arrange - 先借一本书std::string borrowId = TestHelper::CreateBorrowRecord(availableBookId, validUserId);Json::Value request;request["borrowId"] = borrowId;std::string requestJson = TestHelper::JsonToString(request);std::string resultJson;// Actbool result = LoanManagerImpl::ReturnBook(requestJson, resultJson);// AssertEXPECT_TRUE(result);Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_TRUE(resultData["success"].asBool());// 验证借阅记录状态已更新EXPECT_TRUE(TestHelper::IsBorrowRecordReturned(borrowId));// 验证图书状态已恢复为可用EXPECT_FALSE(TestHelper::IsBookBorrowed(availableBookId));
}

3. 仪表板模块测试

// Src/TestFramework/DashBoard/DashBoardTest.cpp
#include <gtest/gtest.h>
#include "Impl/DashBoard/DashBoard.h"
#include "TestFramework/TestHelper.h"using namespace LibrarySystem::Dashboard;class DashBoardTest : public ::testing::Test {
protected:void SetUp() override {TestHelper::InitializeTestDatabase();TestHelper::ClearAllData();// 创建测试数据:10本书,5个用户,3条借阅记录for (int i = 0; i < 10; ++i) {TestHelper::AddTestBook("Test Book " + std::to_string(i));}for (int i = 0; i < 5; ++i) {TestHelper::AddTestUser("User " + std::to_string(i), "user" + std::to_string(i) + "@test.com");}// 创建一些借阅记录TestHelper::CreateSampleBorrowRecords();}void TearDown() override {TestHelper::ClearAllData();TestHelper::CleanupTestDatabase();}
};// 测试获取仪表板统计数据
TEST_F(DashBoardTest, GetDashboardStats_Success) {// Arrangestd::string resultJson;// Actbool result = DashboardImpl::GetDashboardStats(resultJson);// AssertEXPECT_TRUE(result);EXPECT_FALSE(resultJson.empty());Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_TRUE(resultData["success"].asBool());EXPECT_TRUE(resultData.isMember("stats"));Json::Value stats = resultData["stats"];// 验证统计数据EXPECT_EQ(stats["totalBooks"].asInt(), 10);EXPECT_EQ(stats["totalUsers"].asInt(), 5);EXPECT_GT(stats["activeBorrows"].asInt(), 0);EXPECT_GE(stats["availableBooks"].asInt(), 0);// 验证图表数据EXPECT_TRUE(resultData.isMember("borrowTrend"));EXPECT_TRUE(resultData.isMember("categoryDistribution"));Json::Value borrowTrend = resultData["borrowTrend"];EXPECT_TRUE(borrowTrend.isArray());EXPECT_GT(borrowTrend.size(), 0);
}// 测试空数据库的仪表板统计
TEST_F(DashBoardTest, GetDashboardStatsEmptyDB_Success) {// Arrange - 清空所有数据TestHelper::ClearAllData();std::string resultJson;// Actbool result = DashboardImpl::GetDashboardStats(resultJson);// AssertEXPECT_TRUE(result);Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_TRUE(resultData["success"].asBool());Json::Value stats = resultData["stats"];EXPECT_EQ(stats["totalBooks"].asInt(), 0);EXPECT_EQ(stats["totalUsers"].asInt(), 0);EXPECT_EQ(stats["activeBorrows"].asInt(), 0);
}

测试辅助工具

测试助手类实现

// Src/TestFramework/TestHelper.h
#ifndef TEST_HELPER_H
#define TEST_HELPER_H#include <string>
#include <json/json.h>class TestHelper {
public:// 数据库管理static bool InitializeTestDatabase();static void CleanupTestDatabase();static void ClearAllData();static void ClearBookData();static void ClearUserData();static void ClearBorrowData();// JSON工具static std::string JsonToString(const Json::Value& json);static Json::Value StringToJson(const std::string& jsonStr);// 测试数据创建static std::string AddTestBook(const std::string& title);static std::string AddTestUser(const std::string& name, const std::string& email);static std::string CreateBorrowRecord(const std::string& bookId, const std::string& userId);static void CreateSampleBorrowRecords();// 数据验证static bool IsBookExistsInDatabase(const std::string& bookId);static bool IsUserExistsInDatabase(const std::string& userId);static bool IsBorrowRecordExists(const std::string& borrowId);static bool IsBookBorrowed(const std::string& bookId);static bool IsBorrowRecordReturned(const std::string& borrowId);// 测试数据操作static void BorrowBookInTest(const std::string& bookId, const std::string& userId);static void BlockUserInTest(const std::string& userId);static std::string GenerateISBN(int seed);private:static std::string GetTestDatabasePath();static void ExecuteSQL(const std::string& sql);
};#endif // TEST_HELPER_H

Mock和Stub实现

// Src/TestFramework/MockDatabase.h
#ifndef MOCK_DATABASE_H
#define MOCK_DATABASE_H#include <gmock/gmock.h>
#include "Database/DatabaseManager.h"class MockDatabase : public DatabaseManager {
public:MOCK_METHOD(bool, Initialize, (const std::string& dbPath), (override));MOCK_METHOD(void, Shutdown, (), (override));MOCK_METHOD(bool, BeginTransaction, (), (override));MOCK_METHOD(bool, CommitTransaction, (), (override));MOCK_METHOD(bool, RollbackTransaction, (), (override));MOCK_METHOD(bool, InsertBook, (const Json::Value& bookData), (override));MOCK_METHOD(bool, UpdateBook, (const std::string& bookId, const Json::Value& bookData), (override));MOCK_METHOD(bool, DeleteBook, (const std::string& bookId), (override));MOCK_METHOD(bool, GetBookById, (const std::string& bookId, Json::Value& bookData), (override));MOCK_METHOD(int, GetTotalBooksCount, (), (override));MOCK_METHOD(int, GetAvailableBooksCount, (), (override));MOCK_METHOD(Json::Value, GetBorrowTrendData, (int days), (override));
};// 使用Mock的测试示例
class BookManagerMockTest : public ::testing::Test {
protected:void SetUp() override {mockDb = std::make_shared<MockDatabase>();// 注入Mock对象到被测试类中BookManagerImpl::SetDatabaseInstance(mockDb);}void TearDown() override {BookManagerImpl::ResetDatabaseInstance();}std::shared_ptr<MockDatabase> mockDb;
};TEST_F(BookManagerMockTest, AddBook_DatabaseFailure_HandledGracefully) {// ArrangeEXPECT_CALL(*mockDb, InsertBook(::testing::_)).WillOnce(::testing::Return(false));Json::Value bookData;bookData["title"] = "Test Book";std::string bookJson = TestHelper::JsonToString(bookData);std::string resultJson;// Actbool result = BookManagerImpl::AddBook(bookJson, resultJson);// AssertEXPECT_FALSE(result);Json::Value resultData = TestHelper::StringToJson(resultJson);EXPECT_FALSE(resultData["success"].asBool());EXPECT_EQ(resultData["error"].asString(), "Database operation failed");
}

测试覆盖率与质量

代码覆盖率配置

# CMakeLists.txt 中添加覆盖率支持
option(ENABLE_COVERAGE "Enable coverage reporting" OFF)if(ENABLE_COVERAGE)if(CMAKE_COMPILER_IS_GNUCXX)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage -fprofile-arcs -ftest-coverage")set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")endif()
endif()# 添加覆盖率目标
if(ENABLE_COVERAGE)find_program(LCOV_PATH lcov)find_program(GENHTML_PATH genhtml)if(LCOV_PATH AND GENHTML_PATH)add_custom_target(coverageCOMMAND ${LCOV_PATH} --directory . --capture --output-file coverage.infoCOMMAND ${LCOV_PATH} --remove coverage.info '/usr/*' --output-file coverage.infoCOMMAND ${LCOV_PATH} --list coverage.infoCOMMAND ${GENHTML_PATH} -o coverage coverage.infoWORKING_DIRECTORY ${CMAKE_BINARY_DIR})endif()
endif()

性能测试基准

// Src/TestFramework/PerformanceTest.cpp
#include <gtest/gtest.h>
#include <benchmark/benchmark.h>
#include "Impl/BookManager/BookManager.h"
#include "TestFramework/TestHelper.h"// 图书搜索性能测试
static void BM_BookSearch(benchmark::State& state) {TestHelper::InitializeTestDatabase();// 预先插入大量测试数据for (int i = 0; i < 10000; ++i) {TestHelper::AddTestBook("Performance Test Book " + std::to_string(i));}Json::Value query;query["keyword"] = "Performance";query["page"] = 1;query["pageSize"] = 50;std::string queryJson = TestHelper::JsonToString(query);for (auto _ : state) {std::string resultJson;BookManagerImpl::GetBookList(queryJson, resultJson);}TestHelper::CleanupTestDatabase();
}BENCHMARK(BM_BookSearch);// 批量操作性能测试
static void BM_BatchAddBooks(benchmark::State& state) {const int batchSize = state.range(0);for (auto _ : state) {state.PauseTiming();TestHelper::InitializeTestDatabase();state.ResumeTiming();for (int i = 0; i < batchSize; ++i) {Json::Value bookData;bookData["title"] = "Batch Book " + std::to_string(i);bookData["author"] = "Batch Author";bookData["isbn"] = TestHelper::GenerateISBN(i);bookData["category"] = "Test";bookData["publisher"] = "Test Publisher";bookData["publishDate"] = "2024-01-01";std::string bookJson = TestHelper::JsonToString(bookData);std::string resultJson;BookManagerImpl::AddBook(bookJson, resultJson);}state.PauseTiming();TestHelper::CleanupTestDatabase();state.ResumeTiming();}
}BENCHMARK(BM_BatchAddBooks)->Range(100, 1000);// 运行基准测试
BENCHMARK_MAIN();

CI/CD集成

GitHub Actions配置

# .github/workflows/test.yml
name: C++ Testson:push:branches: [ main, develop ]pull_request:branches: [ main ]jobs:test:runs-on: ${{ matrix.os }}strategy:matrix:os: [ubuntu-latest, windows-latest, macos-latest]build_type: [Debug, Release]steps:- uses: actions/checkout@v3- name: Install dependencies (Ubuntu)if: matrix.os == 'ubuntu-latest'run: |sudo apt-get updatesudo apt-get install -y cmake build-essential libgtest-dev libgmock-devsudo apt-get install -y lcov- name: Install dependencies (Windows)if: matrix.os == 'windows-latest'run: |vcpkg install gtest gmock sqlite3- name: Configure CMakerun: |cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build_type}}-DENABLE_TESTING=ON-DENABLE_COVERAGE=ON- name: Buildrun: cmake --build ${{github.workspace}}/build --config ${{matrix.build_type}}- name: Testworking-directory: ${{github.workspace}}/buildrun: ctest -C ${{matrix.build_type}} --output-on-failure- name: Generate Coverage Report (Ubuntu)if: matrix.os == 'ubuntu-latest' && matrix.build_type == 'Debug'working-directory: ${{github.workspace}}/buildrun: make coverage- name: Upload Coverage to Codecovif: matrix.os == 'ubuntu-latest' && matrix.build_type == 'Debug'uses: codecov/codecov-action@v3with:file: ${{github.workspace}}/build/coverage.infofail_ci_if_error: true

本地测试脚本

#!/bin/bash
# scripts/run_tests.shset -e# 配置参数
BUILD_TYPE=${BUILD_TYPE:-Debug}
ENABLE_COVERAGE=${ENABLE_COVERAGE:-ON}
TEST_FILTER=${TEST_FILTER:-"*"}echo "Building and running tests..."
echo "Build Type: $BUILD_TYPE"
echo "Coverage: $ENABLE_COVERAGE"
echo "Test Filter: $TEST_FILTER"# 创建构建目录
mkdir -p build
cd build# 配置CMake
cmake .. \-DCMAKE_BUILD_TYPE=$BUILD_TYPE \-DENABLE_TESTING=ON \-DENABLE_COVERAGE=$ENABLE_COVERAGE# 构建项目
cmake --build . --config $BUILD_TYPE -j$(nproc)# 运行测试
echo "Running unit tests..."
./code/backend/gtester/GTestRunner --gtest_filter="$TEST_FILTER" --gtest_output=xml:test_results.xml# 生成覆盖率报告
if [ "$ENABLE_COVERAGE" = "ON" ]; thenecho "Generating coverage report..."make coverageecho "Coverage report generated in build/coverage/"
fiecho "Tests completed successfully!"

Windows批处理脚本

@echo off
REM scripts/run_tests.batsetlocal EnableDelayedExpansionset BUILD_TYPE=Debug
if not "%1"=="" set BUILD_TYPE=%1set ENABLE_COVERAGE=OFF
if not "%2"=="" set ENABLE_COVERAGE=%2echo Building and running tests...
echo Build Type: %BUILD_TYPE%
echo Coverage: %ENABLE_COVERAGE%REM 创建构建目录
if not exist build mkdir build
cd buildREM 配置CMake
cmake .. ^-DCMAKE_BUILD_TYPE=%BUILD_TYPE% ^-DENABLE_TESTING=ON ^-DENABLE_COVERAGE=%ENABLE_COVERAGE%if errorlevel 1 (echo CMake configuration failed!exit /b 1
)REM 构建项目
cmake --build . --config %BUILD_TYPE%if errorlevel 1 (echo Build failed!exit /b 1
)REM 运行测试
echo Running unit tests...
code\backend\gtester\%BUILD_TYPE%\GTestRunner.exe --gtest_output=xml:test_results.xmlif errorlevel 1 (echo Tests failed!exit /b 1
)echo Tests completed successfully!

测试最佳实践

1. 测试命名规范

// 测试命名模式:方法名_输入条件_期望结果
TEST_F(BookManagerTest, AddBook_ValidData_Success)
TEST_F(BookManagerTest, AddBook_DuplicateISBN_Failure)
TEST_F(BookManagerTest, AddBook_InvalidJSON_Failure)
TEST_F(BookManagerTest, DeleteBook_BorrowedBook_Failure)

2. 测试数据管理

class TestDataManager {
public:// 测试数据构建器模式class BookBuilder {public:BookBuilder& withTitle(const std::string& title) {data["title"] = title;return *this;}BookBuilder& withAuthor(const std::string& author) {data["author"] = author;return *this;}BookBuilder& withISBN(const std::string& isbn) {data["isbn"] = isbn;return *this;}Json::Value build() const {return data;}private:Json::Value data;};static BookBuilder createBook() {return BookBuilder().withTitle("Default Title").withAuthor("Default Author").withISBN("9787111213826");}
};// 使用示例
TEST_F(BookManagerTest, AddBook_CustomData_Success) {auto book = TestDataManager::createBook().withTitle("Custom Book Title").withAuthor("Custom Author").build();std::string bookJson = TestHelper::JsonToString(book);std::string resultJson;bool result = BookManagerImpl::AddBook(bookJson, resultJson);EXPECT_TRUE(result);
}

3. 异步测试处理

// 异步操作测试
TEST_F(BookManagerTest, AsyncBookSearch_LargeDataset_CompletesInTime) {const int timeout_ms = 5000;std::promise<bool> promise;std::future<bool> future = promise.get_future();// 启动异步搜索std::thread searchThread([&]() {try {Json::Value query;query["keyword"] = "test";std::string queryJson = TestHelper::JsonToString(query);std::string resultJson;bool result = BookManagerImpl::GetBookList(queryJson, resultJson);promise.set_value(result);} catch (...) {promise.set_value(false);}});// 等待结果或超时auto status = future.wait_for(std::chrono::milliseconds(timeout_ms));ASSERT_EQ(status, std::future_status::ready);EXPECT_TRUE(future.get());if (searchThread.joinable()) {searchThread.join();}
}

下期预告

在下一篇文章中,我们将介绍CMake构建系统的高级用法与持续集成实践,包括如何设置复杂的构建配置、依赖管理,以及在不同平台上的部署策略。

总结

本文详细介绍了Google Test单元测试的完整实践,包括:

  1. 测试框架搭建: 基于CMake的测试项目组织
  2. 模块化测试: 按业务模块组织的测试结构
  3. 全面测试覆盖: 正常流程、异常处理、边界条件测试
  4. 测试工具: Mock、Stub、测试助手类的使用
  5. 性能测试: 基准测试和性能监控
  6. CI/CD集成: 自动化测试和持续集成流程
  7. 最佳实践: 测试命名、数据管理、异步测试

通过完善的测试体系,我们确保了C++后端代码的质量和稳定性,为整个智能图书馆管理系统提供了可靠的质量保障。

系列文章目录

  1. 项目架构设计与技术选型
  2. 高保真原型设计与用户体验测试
  3. 前端工程化实践:Electron + React + TypeScript
  4. 后端C++ DLL开发与模块化设计
  5. 前后端集成:koffi调用与接口设计
  6. Google Test单元测试实践
  7. CMake构建系统与持续集成
  8. 性能优化与部署发布

通过这个系列文章,您将学习到现代桌面应用开发的完整流程和最佳实践。

程序及源码附件下载

程序及源码

http://www.dtcms.com/a/528774.html

相关文章:

  • React 06
  • 红河县网站建设昆明网站建设哪家
  • 社区互助养老系统设计与实现方案
  • 服装购物商城网站建设安徽六安旅游必去十大景点
  • 「用Python来学微积分」14. 连续函数的运算与初等函数的连续性
  • 红酒商城网站建设广告设计案例网站
  • Linux内核进程管理子系统有什么第六十七回 —— 进程主结构详解(63)
  • 哪个网站可以接针织衫做单淘宝上找人做网站
  • C++容器deque
  • 【NestJS】 OpenAPI文档:运行时动态生成揭秘
  • 关闭VSCode的GitHub Copilot功能
  • 网站页面设计版权企业做网站这些问题必须要注意
  • python opencv gpu加速 cmake msvc cuda编译问题和设置
  • Profibus DP转Modbus TCP工业数据采集网关:实时监测楼宇设备状态
  • HTTP 协议的常用方法有哪些?(GET、POST、PUT、DELETE、PATCH)各自的作用和区别是什么?
  • 什么是 RESTful API?RESTful API 的设计原则有哪些?(URL 语义化、HTTP 方法对应操作、无状态等)
  • 怎么在CSDN插入表格 设置字体颜色
  • Pycatia二次开发基础代码解析:组件识别、选择反转与链接创建技术解析
  • 多线程六脉神剑第五剑:原子操作 (Interlocked)
  • 在阿里云Debian12搭建WG
  • 深度掌握 Git 分支体系:从基础操作到高级策略与实践案例
  • 如何修改dns 快速使用境外网站西安seo公司哪家好
  • 域名对网站有什么影响爱站网长尾词挖掘工具
  • 解码Linux文件IO之开机动画原理与实现
  • R语言模型分析(一)(1)
  • 成都模板建站代理佛山手工外发加工网
  • 二维差分数组
  • 【Linux网络】定制协议
  • wordpress漫画站wordpress 外部调用
  • python 网站开发流程图微信营销软件破解版