diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..4cb68f5 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10.0) +project(test_plan_execute_t1) +include(FetchContent) +if (MSVC) + add_compile_options(/utf-8) +endif() +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include) + +include(CTest) +enable_testing() + +add_executable(test_plan_execute_t1 test_app.cpp ../src/app.cpp) + +target_link_libraries(test_plan_execute_t1 gtest gmock gtest_main) +include(GoogleTest) +gtest_discover_tests(test_plan_execute_t1) \ No newline at end of file diff --git a/tests/test_app.cpp b/tests/test_app.cpp new file mode 100644 index 0000000..fa86206 --- /dev/null +++ b/tests/test_app.cpp @@ -0,0 +1,300 @@ +#include +#include +#include +#include "app.hpp" + +// ============================================================ +// 测试夹具:CmsEngineTest +// ============================================================ +class CmsEngineTest : public ::testing::Test { +protected: + CmsEngine engine; + + void SetUp() override { + // 每个测试用例开始前重置引擎状态 + // 注意:CmsEngine 构造函数已初始化,但 plans_ 为空 + } + + void TearDown() override { + // 清理(如果需要) + } +}; + +// ============================================================ +// 测试用例:createTaskPlan +// ============================================================ + +/** + * @requirement(name="任务方案创建", id="REQ-PLAN-CREATE-001") + * 系统需校验方案名称非空后,为其分配系统 ID 并存入方案库, + * 确保方案数据的完整性和可追溯性。 + */ + +// 正常输入测试:创建有效方案 +TEST_F(CmsEngineTest, testCreateTaskPlanNormal) { + TaskPlan plan; + plan.name = "TestPlan"; + plan.type = PlanType::Centralized; + plan.status = PlanStatus::Drafting; + plan.resourceQuota = 0.8; + plan.constraints = "无约束"; + + bool result = engine.createTaskPlan(plan); + EXPECT_TRUE(result); + EXPECT_FALSE(plan.id.empty()); + EXPECT_EQ(plan.name, "TestPlan"); + EXPECT_EQ(plan.type, PlanType::Centralized); + EXPECT_EQ(plan.status, PlanStatus::Drafting); + EXPECT_EQ(plan.resourceQuota, 0.8); + EXPECT_EQ(plan.constraints, "无约束"); + + // 验证方案已存入引擎 + const auto& plans = engine.getAllPlans(); + ASSERT_EQ(plans.size(), 1); + EXPECT_EQ(plans[0].id, plan.id); + EXPECT_EQ(plans[0].name, "TestPlan"); +} + +// 边界值测试:名称为空字符串 +TEST_F(CmsEngineTest, testCreateTaskPlanEmptyName) { + TaskPlan plan; + plan.name = ""; + plan.type = PlanType::Centralized; + plan.status = PlanStatus::Drafting; + + bool result = engine.createTaskPlan(plan); + EXPECT_FALSE(result); + EXPECT_TRUE(plan.id.empty()); // 不应分配 ID + + // 验证方案未存入引擎 + const auto& plans = engine.getAllPlans(); + EXPECT_EQ(plans.size(), 0); +} + +// 边界值测试:名称为空白字符串(仅空格) +TEST_F(CmsEngineTest, testCreateTaskPlanWhitespaceName) { + TaskPlan plan; + plan.name = " "; + plan.type = PlanType::Centralized; + plan.status = PlanStatus::Drafting; + + bool result = engine.createTaskPlan(plan); + EXPECT_TRUE(result); // 空白字符串不为空,应视为合法 + EXPECT_FALSE(plan.id.empty()); + + const auto& plans = engine.getAllPlans(); + ASSERT_EQ(plans.size(), 1); + EXPECT_EQ(plans[0].name, " "); +} + +// 边界值测试:名称长度极长 +TEST_F(CmsEngineTest, testCreateTaskPlanLongName) { + TaskPlan plan; + plan.name = std::string(1000, 'A'); // 1000 个字符 + plan.type = PlanType::Centralized; + plan.status = PlanStatus::Drafting; + + bool result = engine.createTaskPlan(plan); + EXPECT_TRUE(result); + EXPECT_FALSE(plan.id.empty()); + + const auto& plans = engine.getAllPlans(); + ASSERT_EQ(plans.size(), 1); + EXPECT_EQ(plans[0].name, std::string(1000, 'A')); +} + +// 异常输入测试:创建多个方案,验证 ID 唯一性 +TEST_F(CmsEngineTest, testCreateTaskPlanMultiplePlans) { + TaskPlan plan1; + plan1.name = "Plan1"; + TaskPlan plan2; + plan2.name = "Plan2"; + TaskPlan plan3; + plan3.name = "Plan3"; + + EXPECT_TRUE(engine.createTaskPlan(plan1)); + EXPECT_TRUE(engine.createTaskPlan(plan2)); + EXPECT_TRUE(engine.createTaskPlan(plan3)); + + // 验证 ID 唯一性 + EXPECT_NE(plan1.id, plan2.id); + EXPECT_NE(plan1.id, plan3.id); + EXPECT_NE(plan2.id, plan3.id); + + // 验证方案数量 + const auto& plans = engine.getAllPlans(); + ASSERT_EQ(plans.size(), 3); +} + +// 特殊场景测试:创建方案后,验证 createTime 被设置 +TEST_F(CmsEngineTest, testCreateTaskPlanCreateTimeSet) { + TaskPlan plan; + plan.name = "TimeTest"; + plan.type = PlanType::Centralized; + plan.status = PlanStatus::Drafting; + + auto before = std::chrono::system_clock::now(); + bool result = engine.createTaskPlan(plan); + auto after = std::chrono::system_clock::now(); + + EXPECT_TRUE(result); + EXPECT_GE(plan.createTime, before); + EXPECT_LE(plan.createTime, after); +} + +// ============================================================ +// 测试用例:getAllPlans +// ============================================================ + +/** + * @requirement(name="任务方案列表查询", id="REQ-PLAN-LIST-001") + * 系统需返回全部任务方案列表的引用,以支撑方案全局视图展示。 + */ + +// 正常输入测试:空列表 +TEST_F(CmsEngineTest, testGetAllPlansEmpty) { + const auto& plans = engine.getAllPlans(); + EXPECT_TRUE(plans.empty()); + EXPECT_EQ(plans.size(), 0); +} + +// 正常输入测试:包含多个方案 +TEST_F(CmsEngineTest, testGetAllPlansWithPlans) { + TaskPlan plan1; + plan1.name = "Plan1"; + TaskPlan plan2; + plan2.name = "Plan2"; + TaskPlan plan3; + plan3.name = "Plan3"; + + engine.createTaskPlan(plan1); + engine.createTaskPlan(plan2); + engine.createTaskPlan(plan3); + + const auto& plans = engine.getAllPlans(); + ASSERT_EQ(plans.size(), 3); + EXPECT_EQ(plans[0].name, "Plan1"); + EXPECT_EQ(plans[1].name, "Plan2"); + EXPECT_EQ(plans[2].name, "Plan3"); +} + +// 边界值测试:验证返回的是常量引用,不可修改 +TEST_F(CmsEngineTest, testGetAllPlansConstReference) { + // 编译时验证:const auto& 可以绑定,auto& 会编译错误 + const auto& plans = engine.getAllPlans(); + EXPECT_TRUE(plans.empty()); + + // 以下代码如果取消注释,编译会失败(因为返回 const 引用) + // plans.push_back(TaskPlan()); // 编译错误 +} + +// 特殊场景测试:多次调用返回相同引用 +TEST_F(CmsEngineTest, testGetAllPlansSameReference) { + const auto& plans1 = engine.getAllPlans(); + const auto& plans2 = engine.getAllPlans(); + EXPECT_EQ(&plans1, &plans2); // 应为同一对象 +} + +// ============================================================ +// 测试用例:findPlanById +// ============================================================ + +/** + * @requirement(name="任务方案查找", id="REQ-PLAN-FIND-001") + * 系统需支持根据任务 ID 在方案库中遍历查找对应方案, + * 找到返回方案指针,未找到返回 nullptr。 + */ + +// 正常输入测试:查找存在的方案 +TEST_F(CmsEngineTest, testFindPlanByIdExisting) { + TaskPlan plan; + plan.name = "TestPlan"; + plan.type = PlanType::Centralized; + plan.status = PlanStatus::Drafting; + engine.createTaskPlan(plan); + + const TaskPlan* found = engine.findPlanById(plan.id); + ASSERT_NE(found, nullptr); + EXPECT_EQ(found->id, plan.id); + EXPECT_EQ(found->name, "TestPlan"); + EXPECT_EQ(found->type, PlanType::Centralized); + EXPECT_EQ(found->status, PlanStatus::Drafting); +} + +// 边界值测试:查找不存在的 ID +TEST_F(CmsEngineTest, testFindPlanByIdNonExisting) { + const TaskPlan* found = engine.findPlanById("NON_EXISTENT_ID"); + EXPECT_EQ(found, nullptr); +} + +// 边界值测试:查找空字符串 ID +TEST_F(CmsEngineTest, testFindPlanByIdEmptyId) { + TaskPlan plan; + plan.name = "TestPlan"; + engine.createTaskPlan(plan); + + const TaskPlan* found = engine.findPlanById(""); + EXPECT_EQ(found, nullptr); +} + +// 边界值测试:在空列表中查找 +TEST_F(CmsEngineTest, testFindPlanByIdEmptyList) { + const TaskPlan* found = engine.findPlanById("ANY_ID"); + EXPECT_EQ(found, nullptr); +} + +// 异常输入测试:查找 ID 格式异常 +TEST_F(CmsEngineTest, testFindPlanByIdInvalidFormat) { + TaskPlan plan; + plan.name = "TestPlan"; + engine.createTaskPlan(plan); + + // 查找类似但不同的 ID + const TaskPlan* found = engine.findPlanById(plan.id + "_extra"); + EXPECT_EQ(found, nullptr); +} + +// 特殊场景测试:多个方案中查找特定方案 +TEST_F(CmsEngineTest, testFindPlanByIdMultiplePlans) { + TaskPlan plan1; + plan1.name = "Plan1"; + TaskPlan plan2; + plan2.name = "Plan2"; + TaskPlan plan3; + plan3.name = "Plan3"; + + engine.createTaskPlan(plan1); + engine.createTaskPlan(plan2); + engine.createTaskPlan(plan3); + + // 查找中间方案 + const TaskPlan* found = engine.findPlanById(plan2.id); + ASSERT_NE(found, nullptr); + EXPECT_EQ(found->name, "Plan2"); + + // 查找最后一个方案 + found = engine.findPlanById(plan3.id); + ASSERT_NE(found, nullptr); + EXPECT_EQ(found->name, "Plan3"); +} + +// 特殊场景测试:查找后修改方案(通过非 const 方法) +TEST_F(CmsEngineTest, testFindPlanByIdAndModify) { + TaskPlan plan; + plan.name = "TestPlan"; + engine.createTaskPlan(plan); + + // 通过 findPlanById 获取 const 指针,不能修改 + const TaskPlan* found = engine.findPlanById(plan.id); + ASSERT_NE(found, nullptr); + EXPECT_EQ(found->status, PlanStatus::Drafting); + + // 通过 updatePlanStatus 修改状态 + bool updated = engine.updatePlanStatus(plan.id, PlanStatus::Completed); + EXPECT_TRUE(updated); + + // 再次查找,验证状态已更新 + found = engine.findPlanById(plan.id); + ASSERT_NE(found, nullptr); + EXPECT_EQ(found->status, PlanStatus::Completed); +}