LyraStarterGame 测试文档
本文档来自 LyraStarterGame(UE 5.8)项目的完整测试记录(TestDocs),随博客文章《对UE的教学项目进行测试实录》一起发布。目录可点击跳转。
00-总纲
- 00-测试总纲
- 测试计划
- 用例分级清单
- 13-测试辅助API设计方案
- 15-UnLuaTestDSL设计方案
- 16-LuaDSL-API参考
- 17-Lua测试框架复盘
- 18-封板审查材料
- 19-CQTest代码审查报告
- 20-测试总结报告
01-系统测试
02-专项测试
03-实施
Bug报告
执行计划
LyraStarterGame 测试总纲
本文件记录测试系统的划分、范围定义和讨论结论。
📌 当前状态(2026-08-04):自动化 CQTest 96/96 全绿,手工 Session 1~3 完成,无 🔴 阻塞 Bug,
已按”功能封板”口径封板(2026-08-03)。详见《Lyra 测试总结报告》。🚫 2026-08-02 决策:状态系统(09)不需要,已从测试范围移除(血量/死亡相关验证并入事件测试 DT-01)。
测试系统清单(共 9 个;状态系统已移除)
| 编号 | 系统名称 |
|---|---|
| 01 | 武器与装备系统 |
| 02 | 控制系统 |
| 03 | 相机系统 |
| 04 | HUD |
| 05 | 前端页面 |
| 06 | 音频系统 |
| 07 | 队伍系统 |
| 08 | 初始化链路系统 |
| 10 | 性能测试 |
测试原则
- 黑盒测试为主 — 除非明确标注为自动化测试,否则一律从玩家视角描述
- 不单独测 GAS — Gameplay Ability System 不作为独立系统,相关功能归入各系统内部细则
- 网络测试 — 多线程/复制场景作为每个系统的细则考虑,不设单独模块
- 边界值测试 — 数值测试包含 0 / 负值 / 正常值 三种边界情况
- 测试方式选择(2026-08 决策) — 仅两种方式:
- C++ CQTest:重复性高的用例(数值边界、状态流转、输入模拟、回归验证)
- 手工测试:其余需要视觉/手感/听感判断的用例
- 弃用蓝图功能测试(FunctionalTest 蓝图)— 复杂逻辑在蓝图中难以编写和维护,效率低,回归 C++ CQTest 实现
LyraStarterGame 测试计划
本文档是”测试宪章”与”测试实施”之间的桥梁:从宪章定下”测什么”出发,规划”按什么顺序实现、每个阶段重点做什么、做到什么程度算完成”。
── 2025-07 实践经验 ──
武器切换测试告诉我们:在 Lyra 的 CQTest + FMapTestSpawner 框架下,所有测试本质是集成测试。
HUD 报错不是测试设计有误,而是集成测试的固有噪音。见第 8 节《测试设计原则》。
文档关系
1 | 00-测试总纲.md ← 系统划分 + 测试原则(定"格局") |
1. 范围与统计
11 个测试系统,共计 88 个测试用例(含236个子状态) + 探索性测试 6 轮,覆盖自动化(CQTest / Gauntlet / AutomationDriver)与手工验证。
| 系统 | 用例数 | 自动化 | 手工 | 推荐工具 | 实现状态 |
|---|---|---|---|---|---|
| 01 武器与装备 | 14(含28子状态) | 21 | 12 | CQTest + 手工 | ✅ EQ/WS 已 CQTest;WF 已用蓝图功能测试完成(2026-08-03,与宪章 CQTest 决策不符,先这么用) |
| 02 控制系统 | 13(含46子状态) | 30 | 18 | CQTest | ✅ CQTest 30/30(2026-08-03) |
| 03 相机系统 | 8(含27子状态) | 0 | 23 | 手工 | ✅ 数值 9/9(NV-01/02 单元,2026-08-03);功能手工完成(Session 2) |
| 04 HUD | 5(含22子状态) | 0 | 22 | 手工 | ✅ 手工完成(Session 3,2026-08-03) |
| 05 前端页面 | 5(含18子状态) | 18 | 0 | Spec + Gauntlet | ⏳ Elimination 菜单已实现 |
| 06 音频系统 | 6(含16子状态) | 0 | 16 | 手工 | ✅ 数值 8/8(2026-08-03)+ 手工完成(Session 3) |
| 07 队伍系统 | 5(含14子状态) | 0 | 14 | 手工 | ✅ 手工完成(Session 3,2026-08-03) |
| 08 初始化链路 | 6(含19子状态) | 19 | 0 | CQTest + Gauntlet | ✅ 5/5(ShooterMaps 5 地图,2026-08-03 回归) |
| — | 🚫 不需要(2026-08-02 决策) | ||||
| 10 事件测试 | 13(含46子状态) | 46 | 0 | CQTest + PIENetworkComponent | ✅ 13/13(DT-01/03/05/06/07,2026-08-04 修复后复跑;DT-04 网络暂缓 → 已知缺口) |
| 11 性能测试 | 12 | 12 | 0 | Gauntlet | ❌ 已知缺口(可行性调研:本机 36 实例需 3~6h,封板日不做全量) |
| 探索性测试 | 6 轮 | — | 6 轮 | 手工 | ❌ 已知缺口(封板后 backlog) |
| 合计 | 84(含238子状态) | 148 | 91 + 6轮 |
2. 实现优先级
按风险等级和系统间依赖排序:
| 优先级 | 系统 | 理由 |
|---|---|---|
| P0 基石 | 初始化链路、前端页面 | 游戏必须能启动、能导航到对局,后续所有测试依赖 |
| P1 核心 | 武器与装备 → 控制系统 | 核心玩法闭环:拿起武器 → 移动操作 → 造成伤害 → 死亡/重生 |
| P2 表现 | 相机 → HUD → 队伍 → 音频 | 核心玩法可用后的体验验证,大量手工判断 |
| P3 压轴 | 性能 → 探索性 | 需要其他系统稳定后才有效果 |
3. 实现路线图
阶段一:框架搭建(基石)
目标:确保自动化测试基础设施跑通,游戏能从头启动到进入对局。
| 事项 | 内容 | 对应用例 |
|---|---|---|
| 初始化链路 | 补全 IN-01~05、NV-01 的 CQTest / Gauntlet 实现 | 19 用例 |
| 前端页面 | 补全 FE-02~06 的 Spec 自动化,覆盖菜单→加载→ESC暂停 | 18 用例 |
实现参考:
1 | 已有 ─ Gauntlet: |
完成标准:
FMapTestSpawner加载L_Expanse地图稳定通过- Experience 链路(GameMode → Pawn → AbilitySet → 默认武器)验证完成
- 主菜单 → 选择模式 → 加载 → 进入游戏 全流程自动化可回归
- 无 🔴 阻塞 Bug
阶段二:核心自动化
目标:核心玩法闭环全部可自动化验证。
| 事项 | 内容 | 对应用例 |
|---|---|---|
| 武器功能/数值 | WF-01 |
21 用例 |
| 控制数值 | MV-01 |
10 主用例(含38子状态) |
实现参考:
1 | CQTest(用于 WF-01~02 输入模拟、WS-01~05 数值、控制数值精确验证): |
完成标准:
- 全部用例编写完成并通过(武器 WF-01
02+WS-0105 / 控制 MV-0103+AM-0103+NV-01~04) - 武器切换、射击、换弹、移动、跳跃、Dash、死亡冻结 均可脚本化验证
- 边界值覆盖到数值测试用例(选择性边界分析 B={a-ε, a, (a+b)/2, b})
- 无 🔴 阻塞 Bug
阶段二点五:死亡事件与网络测试
目标:死亡/复活完整链路 + 客户端—服务端网络同步验证。
| 事项 | 内容 | 对应用例 |
|---|---|---|
| 死亡单机 | 状态机、移动/碰撞/交互冻结、摄像机切换、边界情况 | DT-01~05(17 子状态) |
| 死亡网络 | PIE 多开验证 DeathState 复制、预测回滚冲突 | DT-04(2 子状态) |
| 复活 | 血量/装备/技能重置、碰撞/输入恢复、立即死亡 | DT-06~07(9 子状态) |
实现参考:
1 | 已有 ─ CQTest: |
完成标准:
- 死亡单机(DT-01~05,17子状态)全部通过
- 死亡网络(DT-04,2子状态)至少实现基础验证
- 复活(DT-06~07,9子状态)全部通过
- 无 🔴 阻塞 Bug
阶段三:边界值与手工测试
目标:补完需要人工判断的用例,聚焦视觉/手感/听感/跨系统交互。
| 事项 | 内容 | 对应用例 | 方式 |
|---|---|---|---|
| 武器动画 | WA-01 |
手工 | |
| 控制动画/视角 | LC-01 + AN-01 | 手工 | |
| 相机功能/数值 | CM-01/02/05 + NV-01~02(FOV clamp/俯仰限幅) | 手工 | |
| HUD | HD-01~05 | 手工 | |
| 音频功能/数值 | AU-01 |
手工 | |
| 队伍功能 | TM-01~05 | 手工 | |
手工测试执行优先级(在一次手工测试 Session 中按此顺序执行):
1 | Session 1(核心视觉): 武器动画(WA) → 装备动作(EQ-01~05) |
手工测试参考
03-实施/12-测试实施计划.md第 3 节的 Bug 报告模板记录发现。
完成标准:
- 边界值自动化用例(NV-01
02 相机 / NV-0102 音频)编写完成并通过 - 全部手工用例执行完成
- 无 🔴 阻塞 Bug
- 每个手工 Session 的 Bug 全部登记到
TestDocs/Bug报告/
阶段四:压测与探索性测试
目标:在系统稳定后,验证性能边界和跨系统组合 Bug。
| 事项 | 内容 | 对应用例 |
|---|---|---|
| 性能测试 | PF-01~12 × 3 地图(帧率/延迟/逐帧检测) | 36 实例 |
| 探索性测试 | 6 轮 × 90 分钟,跨系统自由探索 | 6 轮 |
性能测试实现参考:
1 | Gauntlet: |
完成标准:
- 全部 36 个性能测试实例通过
- 60fps 下平均帧时间 ≤ 16ms,99% 帧 ≤ 30ms
- 延迟 50~500ms 范围内无断线或操作卡死
- 5 分钟内无持续内存增长
- 探索性 6 轮全部完成,发现 Bug 清零
4. 自动化实现指引
4.1 测试框架选择
| 框架 | 文件位置 | 适用场景 | 入口 |
|---|---|---|---|
| CQTest | Source/LyraGame/Tests/CQTest/ |
单进程、游戏内组件精确验证、数值边界/循环遍历、输入模拟、网络同步 | TEST_CLASS + FMapTestSpawner |
| Spec (AutomationDriver) | Source/LyraGame/Tests/ |
UI 交互流程、菜单导航、前端页面 | BEGIN_DEFINE_SPEC |
| Gauntlet | Source/LyraGame/Tests/ |
多进程、启动链路、性能逐帧采集 | UGauntletTestController 子类 |
4.2 CQTest 标准模式
1 | // 文件: Source/LyraGame/Tests/CQTest/系统名.spec.cpp |
4.3 CQTest 标准模式(含输入模拟)
蓝图功能测试(FunctionalTest 蓝图)已弃用(2026-08 决策),输入模拟类用例统一用 CQTest +
InputTestActions实现。
1 | // 输入模拟: 按下/按住/松开 |
4.4 各系统实现映射
| 系统 | 框架 | 文件/蓝图名 | 用例对应 | 状态 |
|---|---|---|---|---|
| 武器-装备 | C++ CQTest | WeaponSystem_Equipment.spec.cpp |
EQ-01~05 | ✅ 已完成 |
| 武器-功能 | 🟦 蓝图功能测试(实际执行)/ C++ CQTest(宪章) | ShooterTests/Content/Blueprint/weapon/WF_*.uasset(WF-01-01 |
WF-01~02 | ✅ 蓝图完成(2026-08-03 决策:与宪章 CQTest 不符,先按蓝图执行;CQTest 仅 WF-01-04 有 WeaponSystem_Firing.spec.cpp) |
| 武器-数值 | C++ CQTest | WeaponSystem_Numerics.spec.cpp |
WS-01~04 | ✅ 已完成(2026-08-03 基线回归 21/21) |
| 武器-动画 | 手工 | — | WA-01~03 | ✅ 手工完成(Session 1) |
| 控制系统 | C++ CQTest | ControlSystem.spec.cpp |
MV-01 |
✅ 30/30(2026-08-03) |
| 控制数值 | C++ CQTest | 并入 ControlSystem.spec.cpp |
NV-01/03/05 | ✅ 已完成 |
| 相机功能 | 手工 | — | CM-01/02/05 | ✅ 手工完成(Session 2) |
| 相机数值 | C++ CQTest | CameraSystem_Numeric.spec.cpp |
NV-01~02 | ✅ 9/9(2026-08-03) |
| HUD | 手工 | — | HD-01~05 | ✅ 手工完成(Session 3) |
| 前端页面 | Spec + Gauntlet | MenuStartElimination.spec.cpp ✅ |
FE-02~06 | ⏳ 部分已有 |
| 音频 | 手工 | — | AU-01~04 | ✅ 手工完成(Session 3) |
| 音频数值 | C++ CQTest | AudioSystem_Numeric.spec.cpp |
NV-01~02 | ✅ 8/8(2026-08-03) |
| 队伍 | 手工 | — | TM-01~05 | ✅ 手工完成(Session 3) |
TeamSystem_Numeric.spec.cpp(新建) |
🚫 已删(2026-08-03:无队伍数量参数) | |||
| 初始化链路 | C++ CQTest + Gauntlet | InitChain/ShooterMapsInit.spec.cpp + LyraTestControllerBootTest |
IN-01~05 | ✅ 5/5(2026-08-03 回归) |
StatusSystem.spec.cpp(新建) |
🚫 不需要(2026-08-02) | |||
| 事件-死亡 | C++ CQTest + PIENetwork | EventSystem_Death.spec.cpp |
DT-01~07 | ✅ 13/13(DT-04 网络暂缓 → 已知缺口) |
| 性能测试 | Gauntlet | LyraTestController* 扩展 |
PF-01~12 | ❌ 已知缺口(可行性调研,封板后 backlog) |
4.4 已有实现覆盖
| 文件 | 覆盖用例 | 类型 |
|---|---|---|
Tests/CQTest/WeaponSystem_Equipment.spec.cpp |
EQ-01~05(5主用例) | CQTest |
Plugins/GameFeatures/ShooterTests/Content/Blueprint/weapon/WF_*.uasset + 对应地图 |
WF-01-01 |
🟦 蓝图功能测试(2026-08-03 实际执行;与宪章 CQTest 决策不符,先这么用) |
Tests/MenuStartElimination.spec.cpp |
FE-02~06(部分) | Spec |
Tests/LyraTestControllerBootTest.h/.cpp |
启动链路(部分) | Gauntlet |
Tests/LyraTestControllerStartEliminationTest.h/.cpp |
Elimination 启动(部分) | Gauntlet |
Tests/LyraInventoryItemInstance.spec.cpp |
库存物品实例(辅助) | Spec |
Tests/GameplayTagStack.spec.cpp |
GameplayTag 堆栈(辅助) | Spec |
Tests/InitChain/ShooterMapsInit.spec.cpp |
IN-01~02(部分) — 5地图加载+装备+AI | CQTest |
Tests/EventSystem_Death.spec.cpp(计划) |
DT-01~07 — 死亡/复活/网络同步 | CQTest + PIENetworkComponent |
5. 手工测试执行清单
优先级 🔴 高(核心功能,必须通过)
| 编号 | 用例名 | 系统 | 步骤摘要 |
|---|---|---|---|
| EQ-01 | 初始武器状态 | 武器 | 进入游戏 → 检查初始武器 |
| EQ-02 | 武器切换(含滚轮/数字等5种状态) | 武器 | 滚轮/数字键 → 切换武器 |
| EQ-03 | 装备武器 | 武器 | 走近武器 → 拾取 |
| CM-01 | 相机跟随角色移动 | 相机 | 移动角色 → 相机平滑跟随 |
| CM-06 | 贴墙时相机推近 | 相机 | 靠墙 → 相机推近不穿模 |
| HD-01 | 准星显示 | HUD | 持有武器 → 准星可见 |
| HD-07 | 命中敌人显示标记 | HUD | 命中 → 标记出现 |
| HD-18 | 血条显示 | HUD | 进入游戏 → 血条可见 |
| HD-19 | 受伤后血条变化 | HUD | 受伤 → 血条减少 |
| HD-21 | 当前武器信息 | HUD | 持有武器 → 武器名/弹药显示 |
| LC-01 | 鼠标水平视角 | 控制 | 移动鼠标 → 水平旋转 |
| LC-02 | 鼠标垂直视角 | 控制 | 移动鼠标 → 垂直调整 |
| TM-05 | 友军标记 | 队伍 | 观察队友 → 友军标记 |
| TM-06 | 敌军标记 | 队伍 | 观察敌人 → 敌军标记 |
| TM-07 | 友军伤害 | 队伍 | 射队友 → 无伤害/受限 |
优先级 🟡 中(重要体验,尽力通过)
| 编号范围 | 系统 | 内容 |
|---|---|---|
| WF-02-04/05 | 武器 | 换弹/射击互斥(2026-08-03 决策:手工补验)。① 射击中按换弹键 → 射击被打断、执行换弹;② 换弹中按射击键 → 换弹被打断、弹药不恢复、换弹动画停止 |
| EQ-02-03 |
武器 | 快速切换/空槽位/满槽/冷却 |
| WA-01~03 | 武器 | 装备/换弹/射击动画 |
| AN-01 | 控制 | 移动/跳跃/蹲下/Dash/切换动画 |
| LC-01(全部) | 控制 | 手柄视角/反转/灵敏度 |
| CM-01/02/05(全部) | 相机 | 旋转/碰撞恢复/蹲下偏移 |
| HD-01~05(全部) | HUD | 全部手工用例 |
| AU-01~04 | 音频 | 音量/输出/HDR/加载音频 |
| TM-01~05 | 队伍 | 归属/显示/标签/比分 |
6. 质量门禁
📌 封板状态(2026-08-04):按”功能封板”口径(2026-08-03),阶段一
三与阶段二点五的3 完成、Bug 已登记、无 🔴 阻塞 Bug;
单机部分已满足——自动化 96/96 全绿、手工 Session 1
阶段二点五的 DT-04 网络与阶段四(性能 36 实例 / 探索性 6 轮)列入《已知缺口 / 封板后 backlog》,不阻塞封板。
阶段一
-
FMapTestSpawner加载L_Expanse成功,无崩溃 - Experience → GameMode → Pawn → AbilitySet 链路完整
- 初始化链路 6 个主用例(含19子状态)全部通过
- 主菜单 → 选模式 → 加载 → 进游戏 全部可自动化
- 前端页面 5 个主用例(含18子状态)全部通过
阶段二
- 武器功能/数值(WF-01
02 + WS-0105)全部通过 - 控制数值(MV-01
03 + AM-0103 + NV-01~04)全部通过 状态系统(ST-01 + NV-01~02)全部通过(已砍,2026-08-02 决策)- 边界值(0 / 负 / 正常)全部覆盖
- 无 🔴 阻塞 Bug
阶段二点五
- 死亡单机(DT-01~05,17子状态)全部通过
- 死亡网络(DT-04,2子状态)通过
- 复活(DT-06~07,9子状态)全部通过
- DeathState 在 PIE 多开中复制一致
- 无 🔴 阻塞 Bug
阶段三
- 相机/音频 边界值自动化(NV-01
02/NV-0102)全部通过(9/9、8/8) - 手工用例 Session 1~3 全部执行完毕(2026-08-03)
- 手工发现的 Bug 登记到
TestDocs/Bug报告/(4 份) - 无 🔴 阻塞 Bug
阶段四
- 性能测试 36 实例全部通过
- 60fps 帧时间 ≤ 16ms,99% ≤ 30ms
- 500ms 延迟不导致断线
- 5min 帧内存无持续增长
- 探索性 6 轮完成,无残留 Bug
7. 文件索引
| 文档 | 路径 |
|---|---|
| 测试总纲 | TestDocs/00-总纲/00-测试总纲.md |
| 本计划 | TestDocs/00-总纲/测试计划.md |
| 武器与装备宪章 | TestDocs/01-系统测试/01-武器与装备系统.md |
| 控制系统宪章 | TestDocs/01-系统测试/02-控制系统.md |
| 相机系统宪章 | TestDocs/01-系统测试/03-相机系统.md |
| HUD 宪章 | TestDocs/01-系统测试/04-HUD.md |
| 前端页面宪章 | TestDocs/01-系统测试/05-前端页面.md |
| 音频系统宪章 | TestDocs/01-系统测试/06-音频系统.md |
| 队伍系统宪章 | TestDocs/01-系统测试/07-队伍系统.md |
| 初始化链路系统宪章 | TestDocs/01-系统测试/08-初始化链路系统.md |
| 状态系统宪章 🚫(已砍,历史参考) | TestDocs/01-系统测试/09-状态系统.md |
| 事件测试宪章 | TestDocs/01-系统测试/10-事件测试.md |
| 性能测试宪章 | TestDocs/02-专项测试/11-性能测试.md |
| 探索性测试 | TestDocs/02-专项测试/12-探索性测试.md |
| 测试实施指南 | TestDocs/03-实施/13-测试实施计划.md |
| 测试工作流清单 | TestDocs/03-实施/测试工作流清单.md |
| CQTest 参考实现 | Source/LyraGame/Tests/CQTest/WeaponSystem_Equipment.spec.cpp |
| Spec 参考实现 | Source/LyraGame/Tests/MenuStartElimination.spec.cpp |
| Gauntlet 参考实现 | Source/LyraGame/Tests/LyraTestControllerBootTest.h/.cpp |
8. 测试设计原则(2025-07 实践经验)
8.1 认清你用的测试层级
1 | 单元测试 ─────── NewObject 组件,直接调函数,mock 依赖 |
CQTest + FMapTestSpawner 99% 的用例都是集成测试。接受它的特征:
| 特征 | 影响 |
|---|---|
| 会启动完整的游戏堆栈 | HUD / 网络 / 动画系统都在跑 |
| HUD 报错是固有噪音 | 不是测试设计问题,用 CreateWeaponItem() 提供完整数据即可 |
| Server RPC 在同一帧执行 | 不需要 Until 等待(单机 PIE 有权限) |
| 测试覆盖了整个系统 | 断言只测关心的部分,其他部分是”附带运行的” |
8.2 用例划分应在”功能路径”层面,不在”系统”层面
1 | ✅ 正确的视角: |
宪章中的系统划分(武器/控制/HUD)是给手工测试用的。自动化测试的用例划分应当以”玩家操作路径”为单位。
8.3 使用正确类型的物品实例
| 场景 | 做法 |
|---|---|
| 测试槽位管理(不渲染) | NewObject<ULyraInventoryItemInstance>()(裸实例) |
| 测试装备切换(会触发 HUD) | InventoryManager->AddItemDefinition(ItemDef)(真实武器) |
判断依据:该物品是否会被 HUD / 能力系统 / 动画系统读到。会 → 用真实武器。不会 → 用裸实例。
8.4 测试方式选择:CQTest vs 手工(2026-08 决策)
弃用蓝图功能测试。 实践结论:复杂逻辑(输入模拟 + 异步等待 + 状态断言)在 FunctionalTest 蓝图中难以编写和维护,调试效率低,无法帮助提高测试效率。
1 | C++ CQTest(重复性高的需求): |
选择标准:用例是否会被反复回归执行 → 是 → CQTest;否 → 手工测试。
8.5 降低集成测试噪音的方法
- 提供完整数据 — 武器测试用
CreateWeaponItem(),避免裸实例触发 HUD 报错 - 创建极简 Experience — 减少不必要的子系统加载(如无 HUD 的测试 Experience)
- 收窄断言范围 — 只断言你关心的值,日志中的无关错误作为噪音接受
用例分级清单 — 集成测试取舍
2026-08-03 依据《测试计划》§8.4 选择标准(是否会被反复回归执行)与”失败价值”筛选问题产出:
“这个用例失败时,除了’功能坏了’,还能告诉我什么?”
覆盖范围:全部系统宪章(01~08、10、性能测试)。
09 状态系统已移出测试范围(2026-08-02 决策);探索性测试为自由探索,不适用四类标签。
分类说明
| 分类 | 含义 | 处置 |
|---|---|---|
| 保留为集成 | 失败能定位链路断裂、有回归价值、或是下游用例基线 | 留在 CQTest 集成实现 |
| 合并 | 与另一用例断言重叠,或本身是另一个用例的子状态 | 并入目标用例,宪章去掉独立条目 |
| 降级为单元 | 纯配置 / 数值 / 曲线读取,不依赖完整游戏栈 | 用 NewObject / LoadObject 直接断言,不走 FMapTestSpawner |
| 保持手工 | 需要视觉 / 手感 / 听感判断 | 维持手工测试,不占用集成成本 |
| 删除 | 断言被其他用例完全覆盖,无独立信息量 | 从宪章移除 |
01 武器与装备系统
| 用例 | 处理 | 理由 |
|---|---|---|
| EQ-01 初始武器状态 | 合并 → 进图冒烟 | 单断言(已装备实例 > 0),本质是 FMapTestSpawner 健康检查,并入”Pawn 生成”冒烟,不配独立用例 |
| EQ-02-01 滚轮轮切 | 保留为集成 | 切换链路回归;与 -02 合为一个”切换入口”用例的两个子状态 |
| EQ-02-02 数字键切换 | 合并 → EQ-02 | 与 -01 同属切换功能,断言结构相同(槽位索引变化) |
| EQ-02-03 切空槽 | 保留为集成 | 边界行为(无反应保持当前),断言廉价且防回归 |
| EQ-02-04 快速切换 | 保留为集成 | 并发 / 时序风险,回归重灾区 |
| EQ-02-05 切换冷却 | 保留为集成 | 状态机时序,防”无冷却连切”回归 |
| EQ-03 装备武器 | 保留为集成 | 拾取链路(Spawner → 重叠 → 快捷栏),是后续所有武器用例的前提 |
| EQ-04 重复装备 | 合并 → EQ-03 | 同一拾取入口的防重入分支,作为 EQ-03 子状态 |
| EQ-05 槽满拾取 | 合并 → EQ-03 | 同上,拾取三态(正常 / 重复 / 满槽)一个用例覆盖 |
| WF-01-01 单发射击 | 保留为集成 | 弹药 -1 + 出弹,核心回归 + 弹药链路冒烟 |
| WF-01-02 连发射击 | 合并 → WS-03 | “按住持续出弹”与弹药递减断言重叠,打空行为由 WS-03 覆盖 |
| WF-01-03 移动射击 | 保留为集成(低优先) | 移动中射击功能正常;散布放大部分并入 SP-03-04,避免重复 |
| WF-01-04 射击中被打断 | 合并 → DT-01-04 | “死亡中断射击”是死亡禁交互的子集,DT-01-04 已覆盖 |
| WF-02-01 自动换弹 | 保留为集成 | 弹药 0 → 自动换弹是独立状态机路径 |
| WF-02-02 主动换弹 | 保留为集成 | 换弹主路径;吸收 -03 满弹匣分支 |
| WF-02-03 满弹匣换弹 | 合并 → WF-02-02 | 同一入口的边界分支(无变化),并入作为子状态 |
| WF-02-04 射击中换弹 | 保留为集成(与 -05 合成一例) | 互斥时序风险,两方向合成”换弹射击互斥”用例 |
| WF-02-05 换弹中射击 | 合并 → WF-02-04 | 同上,与 -04 合为一个用例的两个子状态 |
| WS-01 距离衰减 | 降级为单元 | GetDistanceAttenuation 是曲线求值,NewObject 武器实例即可;如需端到端只保留 1 个集成冒烟 |
| WS-02 射速 | 降级为单元 | LoadObject 读 fireDelayTimeSecs 配置,无需 PIE(宪章代码思路已注明) |
| WS-03 弹药量 | 降级为单元 + 并入 WF-01-01 | 容量配置读取为单元;递减行为并入 WF-01-01 集成断言 |
| WS-04 部位伤害 | 降级为单元(-03 保留集成) | 倍率数据校验(≥0、≤10)为单元;-03 实际爆头 2 倍伤害保留集成(伤害链路) |
| WA-01~03 动画 | 保持手工 | 视觉 / 手感判断 |
📌 2026-08-03 实际执行:WF-01~02 全组已用蓝图功能测试完成(ShooterTests/Blueprint/weapon/),未按上表”保留为集成”走 CQTest。与宪章决策不符,先按蓝图执行(记录偏差)。WS-03 递减断言由蓝图 WF_01_01/02 覆盖;CQTest 侧
WeaponSystem_Firing.spec.cpp仅覆盖 WF-01-04,与蓝图重复,作补充保留。
02 控制系统
| 用例 | 处理 | 理由 |
|---|---|---|
| MV-01 方向移动 | 保留为集成(四方向合并循环) | 输入 → 移动链路冒烟 + 回归;-01~04 在代码里用循环遍历,不写四个重复断言 |
| MV-01-05 斜向移动 | 保留为集成 | 作为 MV-01 子状态 |
| MV-02 停止移动 | 合并 → MV-01 | “松键后速度归零”是 MV-01 的隐含断言;风险点(快速反向滑行)作为 MV-01 附加断言 |
| MV-03 移动中转向 | 保留为集成 | 视角 → 移动方向转向链路 |
| AM-01-01 平地跳跃 | 保留为集成 | 跳跃链路基线,同时承载 NV-01 的跳高断言 |
| AM-01-02 移动中跳跃 | 保留为集成 | 子状态 |
| AM-01-03 蹲跳 | 删除(并入 AM-02-04) | 与 AM-02-04 完全重复,保留蹲下用例内的那一份 |
| AM-01-04 连续跳跃 | 保留为集成 | 落地时机边界,回归价值高 |
| AM-01-05 空中移动 | 保留为集成 | 空中控制子状态 |
| AM-02-01/02 蹲下/站起 | 保留为集成(合成一例往返) | 碰撞高度变化断言,一个用例两个子状态 |
| AM-02-03 蹲下行进 | 保留为集成 | 速度受限断言,子状态 |
| AM-02-04 蹲跳 | 保留为集成 | 吸收 AM-01-03 |
| AM-02-05 矮通道 | 保持手工 | 几何 / 视觉判断 |
| AM-03 Dash 全部 | 保持手工 | 蓝图 GA,已决策转手工 |
| LC-01 视角控制 | 保持手工 | 手感判断 |
| AN-01 动画 | 保持手工 | 视觉判断 |
| NV-01 跳跃速度 | 合并 → AM-01 | 中间值 / 最大值 = 跳高实测,并入 AM-01;a-ε / 0 风险点降级为单元 clamp 校验 |
| NV-02 Dash 冷却 | 保持手工 | 已决策 |
| NV-03 重力 | 合并 → AM-01 | 下落行为并入跳跃用例;负重力 / 除零风险点降级为单元 |
| NV-04 灵敏度 | 保持手工 | 手感判断 |
| NV-05 MaxWalkSpeed | 合并 → MV-01 | 半速 / 全速断言并入方向移动;负值 / 0 风险点降级为单元 |
| FR-01/02 死亡冻结 | 删除 | 与 DT-01(移动冻结)、DT-06-04(重生恢复)断言重复,实现 DT 后移除 |
10 事件测试
| 用例 | 处理 | 理由 |
|---|---|---|
| DT-01 死亡状态机与冻结 | 保留为集成 | 死亡状态机核心链路,吸收 FR-01 |
| DT-02 死亡摄像机 | 合并 → DT-01-05(-01 相机切换);-02 保持手工 | 相机模式切换需校验模式栈,手工无法判断(2026-08-03);重生恢复可观察 |
| DT-03 死亡状态标签 | 保留为集成 | GAS 标签断言,回归价值高 |
| DT-04 网络同步 | 保留为集成(后续排期) | 复制 / 回滚是高风险区,暂缓但不可删 |
| DT-05-01 连续致死 | 保留为集成 | 状态机幂等,防双重广播 |
| DT-05-02 空中死亡 | 合并 → DT-01-03 | 与”死亡后碰撞禁用”同链路的变体,并入碰撞冻结用例 |
| DT-05-03 蹲伏中死亡 | 保留为集成 | 碰撞高度残留风险 |
| DT-05-04 Dash 中死亡 | 保持手工 | 已决策 |
| DT-06 重生完整性 | 保留为集成 | 状态重置回归核心,吸收 FR-02 |
| DT-07-01 重生后立即死亡 | 保留为集成 | 时序风险(重生 → 死亡快速转换) |
| SP-01 热量累积 / 冷却 | 保留为集成 | 数值状态机核心;HeatPerShot=0 除零风险点降级为单元 clamp 校验 |
| SP-02-01 曲线求值 | 降级为单元 | 曲线求值无需游戏栈 |
| SP-02-02/03 弹孔散布 | 保留为集成 | 实际弹孔分布需要 PIE |
| SP-03 修正乘数 | 保留为集成 | 状态组合 + 过渡(吸收 WF-01-03 散布断言) |
| SP-04 首发精度 | 保留为集成 | 状态标志回归 |
| SP-05 冷却延迟 | 保留为集成(-03 降级单元) | 时序断言保留;负值 clamp 校验为单元 |
| SP-06 曲线边界 | -01 降级为单元,-02/03 保留集成 | ClampMin 校验为单元;分布实测为集成 |
其它系统(03~08、性能)
| 系统 | 主要用例目的 | 说明 |
|---|---|---|
| 03 相机系统 | CM-01/02/05 🟠 验收;NV-01/02 ⚪ 回归·单元 | 数值边界自动化,功能靠视觉/手感(CM-03/CM-04 已删:无可调 FOV / 无相机切换;NV-03 已删) |
| 04 HUD | 全部 🟠 验收 | 纯视觉判断,无自动化 |
| 05 前端页面 | FE-02/05 🔵 冒烟;其余 🟢 回归 | 启动/加载链路为冒烟,流程类为回归(FE-01 已删:与 FE-05 重复) |
| 06 音频系统 | AU-01~04 🟠 验收;NV-01/02 ⚪ 回归·单元 | 听感手工,音量边界走单元 |
| 07 队伍系统 | TM-01~05 🟠 验收 | 多人场景手工(NV-01 已删:游戏无队伍数量参数) |
| 08 初始化链路 | IN-01/02/04 🔵 冒烟;IN-03/05、NV-01 🟢 回归 | 链路基线为主;IN-03 与 DT-06 重叠,建议并入 |
| 性能测试 | PF-01~12 全部 🟢 回归 | 性能门禁:帧时间 / 内存 / 网络延迟 |
明细汇总(01/02/10 三系统)
| 系统 | 保留集成 | 合并 | 降级单元 | 保持手工 | 删除 |
|---|---|---|---|---|---|
| 01 武器与装备 | 8 | 7 | 4 | 3 | 0 |
| 02 控制系统 | 4 | 5 | 3(部分子状态) | 6 | 2 |
| 10 事件测试 | 11 | 1 | 4(部分子状态) | 2 | 0 |
落地建议
宪章加”用例目的”列✅ 已完成(全部系统宪章,2026-08-03):为每个用例标注 冒烟 / 回归 / 回归·单元 / 验收。- 实现时按清单合并:MV-01 四方向循环、EQ-03 拾取三态、WF-02-04/05 互斥一例,先合并再写代码,避免重复断言堆量。
- NV / WS 风险点统一走单元:负值 / 零值 clamp 校验全部降级为 NewObject / LoadObject 直接断言,只在行为类用例(AM-01、MV-01)里保留实测断言。
- FR-01/02 在 DT-01 / DT-06 实现通过后移除,避免两套用例断言同一件事。
13 — 测试辅助 API 设计方案
⚠️ 状态:冻结(2026-08 决策):蓝图功能测试已弃用,回归 C++ CQTest + 手工。
本方案的”蓝图用轮子”设计前提不再成立(LyraTestUtilities 以BlueprintCallable蓝图分类优先)。
如需测试辅助 API,直接以 C++ 函数形式提供给 CQTest 使用,不再以蓝图分类为优先。
本文档仅作历史参考。
本文档记录 LyraTestUtilities 模块的设计思路、API 规范和使用方法。
属于框架层面的设计方案,不涉及具体测试用例。
1. 背景与动机
1.1 问题
UE 内置的蓝图功能测试(AFunctionalTest)提供了基础框架,但缺少以下能力:
| 缺失能力 | 影响 |
|---|---|
| 输入模拟(按住/点击/释放) | 无法模拟玩家操作 |
| Gameplay 系统查询 | 无法直接读取弹药/血量/武器状态 |
| Actor 查找与生成 | 需要在测试中手动处理 |
1.2 设计理念
C++ 造轮子,蓝图用轮子。流程控制交给 EnhancedCodeFlow(ECF)插件:
1 | C++ (LyraTestUtilities / LyraGame) 蓝图 (FunctionalTest + ECF) |
1.3 架构分层
| 层 | 负责 | 技术方案 |
|---|---|---|
| 流程控制 | 循环、等待、条件判断 | EnhancedCodeFlow(While True Execute、Delay、协程) |
| Lyra 专有查询 | 弹药/血量/武器/标签 | LyraTestGameplayLibrary(LyraGame 内部) |
| 输入模拟 | 注入增强输入 | LyraTestInputLibrary |
| 世界/工具 | 获取 Actor、传送、移动 | LyraTestWorldLibrary / UtilityLibrary |
1.4 设计目标
- 最小侵入 — 不修改 LyraGame 核心逻辑,不碰已有头文件
- 蓝图优先 — 所有函数标记
BlueprintCallable,以Lyra|Test|分类出现 - 只读优先 — 查询操作不修改游戏状态
- 条件编译 — 仅在非 Shipping 构建中编译
2. 模块结构
2.1 模块信息
| 属性 | 值 |
|---|---|
| 模块名 | LyraTestUtilities |
| 类型 | Runtime(仅非 Shipping 构建) |
| 路径 | Source/LyraTestUtilities/ |
| 依赖 | Core Engine EnhancedInput AIModule |
2.2 Gameplay 查询特殊归属
ULyraTestGameplayLibrary 位于 LyraGame 内部(非 LyraTestUtilities),原因:
- 需要访问 Lyra 内部类(
ULyraQuickBarComponent、ULyraInventoryItemInstance等) - 这些类没有
LYRAGAME_API导出宏,外部模块无法链接 - 作为 LyraGame 的新建文件存在,不修改已有代码
路径:
Source/LyraGame/Public/TestUtilities/LyraTestGameplayLibrary.hSource/LyraGame/Private/TestUtilities/LyraTestGameplayLibrary.cpp
2.3 文件组织
1 | Source/LyraTestUtilities/ |
2.4 编译控制
在 LyraGame.Target.cs 的 ApplySharedLyraTargetSettings() 中条件添加:
1 | if (!bIsShipping && !bIsTest) |
Shipping/Test 配置下模块不会被编译。
3. 函数库 API 参考
3.1 ULyraTestInputLibrary — 输入模拟
蓝图分类: Lyra|Test|Input
实现方式:通过 UEnhancedInputLocalPlayerSubsystem::InjectInputVectorForAction 注入输入信号,无需真实硬件。
| 函数 | 参数 | 说明 |
|---|---|---|
Click |
PC, Action, Value=(1,0,0) |
模拟按下 → 延迟 2 帧 → 释放 |
Press |
PC, Action, Value=(1,0,0) |
模拟按下(Inject Started) |
Release |
PC, Action |
模拟释放(终止 Hold + FlushPressedKeys) |
Hold |
WorldContext, PC, Action, Value=(1,0,0) |
持续按住(每帧注入),直到 Release 停止 |
注意事项:
Click内部使用BindWeakLambda延迟释放,PC 销毁时自动取消Hold+Release可模拟长时间按住(如持续移动)
与 ECF 配合示例:
1 | While True Execute |
3.2 ULyraTestWorldLibrary — 世界查询
蓝图分类: Lyra|Test|World
| 函数 | 参数 | 返回 | 说明 |
|---|---|---|---|
GetPlayerController |
WorldContext, PlayerIndex=0 |
APlayerController* |
获取指定索引的玩家控制器 |
GetPlayerPawn |
WorldContext, PlayerIndex=0 |
APawn* |
获取指定索引的玩家 Pawn |
SpawnActor |
WorldContext, Class, Transform |
AActor* |
在世界中生成本地 Actor |
FindActorByClass |
WorldContext, Class |
AActor* |
查找第一个指定类型的 Actor |
等待操作已移除: 改用 ECF 的 Delay / Delay Ticks / 协程。
3.3 ULyraTestGameplayLibrary — Gameplay 系统查询
蓝图分类: Lyra|Test|Gameplay
位置: LyraGame/Public/TestUtilities(非 LyraTestUtilities 模块)
| 函数 | 参数 | 返回 | 说明 |
|---|---|---|---|
GetCurrentWeapon |
PC |
AActor* |
获取当前装备的远程武器 Actor |
GetAmmo |
PC |
int32 |
获取弹匣内剩余弹药(读 StatTagStack MagazineAmmo) |
GetMagazine |
PC |
int32 |
获取弹匣总容量 |
GetHealth |
PC |
float |
获取当前生命值 |
HasGameplayTag |
Actor, Tag |
bool |
检查是否拥有指定 GameplayTag |
AmmoChanged |
PC |
bool |
弹药量是否自上次调用时变化(用于 ECF While 循环的条件判断) |
与 ECF 配合示例 —— 连射到空弹匣:
1 | While True Execute (ECF) |
等待条件示例(用 ECF 协程):
1 | ECF Coroutine: |
注意事项:
- 需要玩家 Pawn 上存在
ULyraQuickBarComponent(Lyra 默认角色已包含) GetMagazine需要武器数据资产中配置了UInventoryFragment_SetStats- 弹药 GameplayTag:
Lyra.ShooterGame.Weapon.MagazineAmmo
3.4 ULyraTestUtilityLibrary — 工具函数
蓝图分类: Lyra|Test|Utility
| 函数 | 参数 | 返回 | 说明 |
|---|---|---|---|
LookAt |
PC, Target |
无 | 使玩家控制器看向目标 Actor |
LookAtLocation |
PC, Location |
无 | 使玩家控制器看向指定位置 |
MoveTo |
Pawn, Location |
无 | 命令 Pawn 移动到位置(AI 下有效) |
TeleportTo |
Actor, Location, Rotation, bSweep=false |
bool |
传送 Actor |
4. 版本记录
| 日期 | 版本 | 变更内容 |
|---|---|---|
| 2026-07-19 | v1.0 | 初始版本 — 5 个函数库 + AsyncCondition |
| 2026-07-22 | v1.1 | 采用 ECF 的 While True/Coroutine 替代等待方法 |
| — 删除 WaitUntil/WaitWhile/WaitUntilAmmoChanged | ||
| — 删除 Wait/WaitFrame/WaitFrames | ||
| — 精简依赖(取消 LyraGame/GameplayTags) | ||
| — 保留 AmmoChanged 作为纯查询函数(ECF 循环条件用) |
文档结束
15 — Lua Test DSL 设计方案(基于 LuaMachine)
本文档记录 LuaMachine + Test DSL 测试框架的架构设计、实施阶段、验收标准与风险分析。
目标:用 Lua DSL 替代蓝图作为主力测试编写方式(80% Lua + 20% C++),蓝图退居辅助。
⚠️ v1.2 重大变更:底层 Lua 引擎从 UnLua 切换为 LuaMachine(复核发现 UnLua 不兼容 UE 5.8)。
本文档属于框架层面的设计方案,不涉及具体测试用例。
1. 背景与动机
1.1 现状
LyraStarterGame 已建立两层半测试体系:
| 层 | 编写方式 | 框架 | 优点 | 痛点 |
|---|---|---|---|---|
| C++ 单元/集成 | .spec.cpp |
CQTest | 编译期检查、性能最优 | 编译慢、门槛高 |
| BP FunctionalTest | 蓝图节点 | AFunctionalTest + ECF | 可视化、策划友好 | 二进制 diff、版本控制不友好 |
| LyraTestUtilities | C++ 函数库 | BlueprintFunctionLibrary | 被蓝图调用、复用 C++ 能力 | 仅作为桥梁,不改变蓝图编写模式 |
设计出发点:C++ 造轮子,Lua 用轮子。流程控制内置于 DSL Runtime(参考 13-测试辅助API设计方案)。
底层 Lua 引擎:**LuaMachine**(v2025-06-04,活跃维护,支持 UE5,Luau 方言)。
1.2 动机
蓝图功能测试在实际迭代中暴露致命问题,且随着测试规模膨胀愈演愈烈:
| 问题 | 说明 |
|---|---|
| 版本控制不友好 | .uasset 二进制格式,diff/merge 几乎不可能,PR review 只能看图 |
| 批量修改困难 | 无法用文本工具全局搜索/替换,重构代价高 |
| CI 文本化报告难 | 蓝图层执行结果需额外手段导出为文本格式 |
| 编写效率 | 复杂流程(多步断言、循环等待)节点连线冗长,不如文本代码紧凑 |
| 编辑器依赖 | 写测试必须打开 UE 编辑器,无法在轻量编辑器中快速编写 |
引入 Lua DSL 的目标是替代蓝图作为主力测试编写方式,而非再增加一个并行选项。
最终测试工作流比例:
1 | ┌──────────────────────────────────────┐ |
- Lua DSL(80%):大部分功能测试、集成测试、回归测试 → 热重载、文本 diff、CI 友好
- C++ CQTest(20%):需要编译期检查的底层单元测试、纯数值/数学验证
- 蓝图:不再推荐用于编写新测试;已有蓝图测试逐步迁移到 Lua
1.3 设计目标
- Lua 为主力 — 80% 测试用 Lua DSL 编写,蓝图不再推荐用于新测试
- 复用已有 C++ 轮子 — 通过 LuaMachine 手动注册绑定调用 LyraTestUtilities 全部 API(12 个函数,胶水代码量可控)
- DSL 声明式 + 简洁 — 测试用例结构清晰,接近自然语言,降低 Lua 学习成本
- 热重载 — 修改
.lua文件后lua.reload()即可生效 - 统一测试入口 — 注册到 UE Automation Framework,与 CQTest 同一面板
- CI 友好 — 命令行可执行、JSON 报告输出、非零退出码
- 零侵入 — 作为独立插件存在,不影响 LyraGame 核心逻辑
2. 架构分层
1 | ┌──────────────────────────────────────────────────────────┐ |
2.1 三层职责
| 层 | 负责 | 技术 |
|---|---|---|
| 测试编写层 | 用户编写测试用例 | .lua 文本文件(DSL)为主 + .spec.cpp(CQTest 底层) |
| LuaMachine 桥接层 | Lua 调用 UE C++ API | LuaMachine 手动注册 + ULuaTestBridge 薄封装 |
| C++ 轮子层 | 核心测试能力 | LyraTestUtilities + CQTest + Automation Framework |
2.2 与现有体系的关系
1 | 现有(即将被替代):C++ → LyraTestUtilities → BP FunctionalTest → ECF(流程控制) |
替代路线:
🔄 新测试 → 一律用 Lua DSL 编写(除非需要编译期检查 → CQTest)
🔄 已有 BP FunctionalTest → 按优先级逐步迁移到 Lua DSL(Phase 6)
🔄 蓝图仅保留作为 Lua DSL 尚不支持的兜底方案
不碰:
❌ LyraGame 核心逻辑
❌ CQTest 已有测试(C++ 底层测试保持不动)
Phase 依赖关系
1 | Phase 1 (LuaMachine 集成) ──┬──→ Phase 2 (DSL Runtime) ──┬──→ Phase 4 (Automation 集成) |
- Phase 1 是前提 — 所有后续 Phase 依赖 LuaMachine 可用
- Phase 2 和 Phase 3 可并行 — DSL Runtime(纯 Lua)和 C++ 桥接(纯 C++)互不依赖
- Phase 4 依赖 Phase 2+3 — 需要 DSL 语法可用 + C++ API 可用
- Phase 5 依赖 Phase 4 — 需要 Automation 注册完成后才能 CLI 执行
- Phase 6 可尽早启动 — 迁移指南和用户手册的部分内容可从 Phase 2 开始并行编写
3. 实施阶段
3.1 Phase 1 — LuaMachine 插件集成与基础设施
目标:引入 LuaMachine 插件,验证基础 Lua ↔ UE 互操作能力,搭建测试脚本目录结构。
背景:原方案使用 UnLua,经复核确认 UnLua 最新 release (v2.3.6, 2023-11) 仅支持到 UE 5.3,项目已实质停滞。切换为 LuaMachine — 当前唯一活跃维护的 UE Lua 插件(2025-06 release),采用更保守的手动注册架构,对 UE 版本升级敏感度低。
工作项:
- 从 rdeioris/LuaMachine 获取最新 release,安装到
Plugins/LuaMachine/ - 在
LyraStarterGame.uproject注册插件 - 配置项目 Lua 脚本目录
Content/Scripts/ - 创建
Content/Scripts/Tests/作为测试脚本根目录 - 学习 LuaMachine 的 API 注册模式:
- 每个需要暴露给 Lua 的 C++ 函数需要在派生类中通过
LuaValue::Function手动注册 - Luau 方言特性:类型注解、
if-then-else表达式、复合赋值等
- 每个需要暴露给 Lua 的 C++ 函数需要在派生类中通过
- 编写 Smoke 验证用例:
- 创建
ULuaTestRunnerActor,在BeginPlay中创建 Lua 状态并执行测试脚本 - Lua 侧调用 C++ 注册的
PrintString输出日志
- 创建
- 验证热重载:通过自定义控制台命令
lua.reload重新加载脚本后执行新代码 - 编写
ULuaTestBridgeC++ 基类,作为所有 Lua 测试的入口点。提供:static void RegisterAll(LuaState)— 在所有模式下注册 C++ API 到 LuaULuaTestRunner(派生 Actor)— P1 验证用,在BeginPlay中创建 VM +RegisterAll+ 执行脚本ULuaFunctionalTest(派生 AFuncTest)— P3 实现,PIE 关卡放置用
验收标准:
| 编号 | 验收项 | 验证方式 |
|---|---|---|
| ✅ P1.1 | LuaMachine 插件在 UE 5.8 编辑器正常加载,无启动报错 | 编辑器启动日志无 LuaMachine 相关 Error |
| ✅ P1.2 | Content/Scripts/Tests/ 目录创建,LuaMachine 可扫描该路径 |
.lua 文件放在该路径下可被 LuaMachine 加载 |
| ✅ P1.3 | ULuaTestRunner Actor 在 PIE 中创建 Lua 状态并执行测试脚本 |
Output Log 显示 Lua 脚本的 print 输出 |
| ✅ P1.4 | 热重载:通过 ULuaTestRunner::ConsoleCommand("lua.reload") 触发重载后新代码生效 |
修改 Lua 脚本 → 执行 reload 命令 → PIE 中运行新逻辑 |
| ✅ P1.5 | C++ 函数可通过 LuaMachine 注册到 Lua 全局表 | ULuaTestBridge::RegisterAPI() 注册的 UE.PrintString 在 Lua 中可调用 |
| ✅ P1.6 | Luau 方言语法可正常使用(类型注解不报错) | 含 --!strict 和类型注解的 .lua 文件正常加载执行 |
3.2 Phase 2 — Test DSL 语言设计与 Runtime 实现
目标:定义 DSL 语法规范,实现 Lua 侧 DSL Runtime(解析器 + 断言 + 执行器)。
工作项:
- 定义 DSL 语法 EBNF(Lua table 声明式)
- 实现 DSL 核心模块:
TestRunner.lua— 测试发现、标签过滤、执行调度TestContext.lua— 测试上下文(Setup / Execute / Teardown 生命周期管理)Assertion.lua— 断言链(expect(x):to_equal(y)/:to_be_truthy()等)
- 实现生命周期钩子:
before_all/after_all/before_each/after_each - 实现参数化测试:
test.each(data)语法 - 实现超时控制:默认 30 秒,可配置
- 编写语法规范文档(独立章节,见 16-LuaDSL-API参考)
DSL 示例:
1 | local suite = TestSuite("武器系统", { |
验收标准:
| 编号 | 验收项 | 验证方式 |
|---|---|---|
| ✅ P2.1 | test() / TestSuite() 语法正确解析,不报 Lua 语法错误 |
在 Lua 解释器中加载 DSL 模块无报错 |
| ✅ P2.2 | expect(x):to_equal(y) / :to_be_truthy() / :to_be_falsy() / :to_contain(v) / :to_throw(fn) 五个断言可用 |
每个断言写单独的肯定/否定用例,全部通过 |
| ✅ P2.3 | before_each / after_each 钩子在每个 test 前后正确执行 |
在钩子和 test 中各打一条日志,验证顺序 |
| ✅ P2.4 | test.each(table_data) 参数化:同一 test 对多组数据各执行一次 |
3 组参数 → 3 次执行结果,失败时显示对应数据行 |
| ✅ P2.5 | 超时控制:默认 30s,可配;超时后标记为 Fail 并输出 “Test timed out” | 写一个死循环 test,设 timeout=2,执行后 2 秒内 Fail |
| ✅ P2.6 | 标签过滤:--filter-tags weapon,smoke 只执行匹配标签的测试 |
准备 3 个不同 tag 的 test,过滤后只执行 2 个 |
| ✅ P2.7 | Luau coroutine.create / coroutine.resume / coroutine.yield 在 LuaMachine 嵌入环境中行为与标准 Lua 5.1 一致 |
写一个简单协程:yield 后由 C++ FTimerHandle resume,验证返回值正确传递 |
3.3 Phase 3 — C++ ↔ Lua 桥接层(手动注册)
目标:LyraTestUtilities 全部 12 个 API 通过 LuaMachine 手动注册到 Lua 侧,类型无损传递,Latent 操作支持协程。
与 LuaMachine 方案的核心差异:
| 维度 | UnLua(不可用) | LuaMachine(当前方案) |
|---|---|---|
| API 暴露 | 自动反射绑定,零胶水代码 | 手动 AddFunction 注册,12 个函数 ≈ 120 行胶水代码 |
| 类型转换 | 自动 | 手动实现 FVector/FRotator/FName ↔ Lua table 转换 |
| 架构 | 侵入 UE 反射系统内部 | 独立 Lua VM,不碰 UE 内部 |
| 维护前景 | 已停滞 | 活跃维护(2025-06 release) |
工作项:
- 编写
ULuaTestBridgeC++ 类(Plugins/LuaTestFramework/Source/),提供静态方法RegisterAll(LuaState):- 注意:不依赖 Actor 生命周期 — Automation 模式下无 World,必须支持纯静态注册
- 封装
LyraTestInputLibrary→ 注册为 Lua 全局函数Click/Press/Release/Hold - 封装
LyraTestWorldLibrary→ 注册为GetPC/GetPawn/SpawnActor/FindActor/LoadMap - 封装
LyraTestGameplayLibrary→ 注册为GetAmmo/GetMagazine/GetHealth/GetCurrentWeapon/HasTag - 封装
LyraTestUtilityLibrary→ 注册为Teleport/LookAt/MoveTo
- 实现类型转换层(手动):
FVector↔ Lua{X, Y, Z}FRotator↔ Lua{Pitch, Yaw, Roll}FName↔ LuastringFGameplayTag↔ LuastringTSubclassOf<AActor>↔ Luastring(资产路径)- UObject 指针 → Lua 侧用整数 handle 或轻量 userdata
- 实现 Latent Action 协程桥接:
ctx:Wait(seconds)→ Luacoroutine.yield+ C++FTimerHandleresumectx:WaitFrames(n)→ Luacoroutine.yield+ C++FTickableGameObject计数 resumectx:WaitUntil(fn, timeout)→ 每帧 poll fn(C++ 侧Tick→ 回调 Lua) + 超时
- 实现
ULuaFunctionalTest(继承AFunctionalTest):- 挂载 Lua 脚本路径
- 在
PrepareTest/StartTest/Tick中调用 Lua 对应生命周期 - 支持 PIE 模式下在关卡中放置并自动执行
验收标准:
| 编号 | 验收项 | 验证方式 |
|---|---|---|
| ✅ P3.1 | 全部 12 个 LyraTestUtilities API 在 Lua 中可调用 | 每个 API 写一个 Lua 调用并检查返回值 |
| ✅ P3.2 | FVector / FRotator 在 Lua ↔ C++ 间正确传递 |
调用注册的 ctx:GetPawnLocation() 返回 {X, Y, Z};ctx:Teleport(actor, pos) 将表正确解析为 FVector |
| ✅ P3.3 | FGameplayTag 从 Lua string 转换为 C++ Tag,HasTag 查询正常 |
ctx:HasTag(actor, "Lyra.ShooterGame.Weapon.MagazineAmmo") 返回 true/false |
| ✅ P3.4 | ctx:Wait(3) 在 Lua 协程中暂停 3 秒后继续 |
记录前后时间戳,差值 ≤ 3.0 ± 0.2 秒 |
| ✅ P3.5 | ctx:WaitFrames(5) 在 Lua 协程中等待 5 帧后继续 |
与 UE Tick 对齐验证 |
| ✅ P3.6 | ULuaFunctionalTest 可在关卡中放置,PIE 中自动执行 Lua 脚本 |
放置到 L_Expanse,PIE 中触发,输出日志含测试结果 |
| ✅ P3.7 | 手动注册胶水代码总量 ≤ 200 行 C++ | 行数统计 |
3.4 Phase 4 — 测试运行器与 Automation 集成
目标:Lua 测试可在 UE Automation Framework 中发现、执行,并生成统一报告。
工作项:
- 实现
FLuaTestAutomationProvider:将 Lua 测试用例注册到 Automation Framework- 用
IFileManager::IterateDirectoryRecursively扫描Content/Scripts/Tests/下所有*_spec.lua文件 - 对每个文件:创建临时 Lua VM →
require该脚本(不调run(),仅获取TestSuite元数据)→ Lua 回调 C++ 注册test_*→ 销毁临时 VM → 注册到FAutomationTestFramework
- 用
- 实现三种执行模式:
- 编辑器 UI:Session Frontend → Automation 面板 → 勾选/运行 Lua 测试
- 命令行:
UEEditor-Cmd.exe LyraStarterGame.uproject -ExecCmds="Automation RunTests LuaTests.Weapon.*" -Unattended -NullRHI - MCP:通过 UnrealMCP
AutomationTestToolset发现和触发 Lua 测试
- 统一报告格式:Lua 测试结果对接
FAutomationTestFramework::Get().GetReport() - 失败时输出 Lua 错误堆栈和断言上下文(期望值 vs 实际值)
验收标准:
| 编号 | 验收项 | 验证方式 |
|---|---|---|
| ✅ P4.1 | Session Frontend Automation 面板中可见 LuaTests.* 条目 |
打开 Automation 面板,搜索 “LuaTests”,出现已注册的测试 |
| ✅ P4.2 | 在 UI 中勾选并运行 Lua 测试,结果(Pass/Fail)正确显示 | 准备 1 个 Pass + 1 个 Fail 测试,面板中分别显示绿色勾和红色叉 |
| ✅ P4.3 | 命令行 -ExecCmds="Automation RunTests LuaTests.*" 可执行并返回非零退出码(失败时) |
echo %ERRORLEVEL% → 失败时非零 |
| ✅ P4.4 | UnrealMCP AutomationTestToolset 可发现并执行 Lua 测试 |
通过 MCP HTTP 调用 /mcp,返回 JSON 含 LuaTests 条目 |
| ✅ P4.5 | 失败时 Output Log 包含 Lua 堆栈和断言详情 | 故意让 to_equal(1, 2) 失败,Output Log 显示 expected 1 but got 2 |
3.5 Phase 5 — 工具链与 CI/CD 集成
目标:命令行执行、JSON 报告、CI Pipeline 示例。
工作项:
- 编写
RunLuaTests.bat/RunLuaTests.ps1:封装 UE 命令行执行 - JSON 报告生成:测试名 / 状态 / 耗时 / 失败原因 / 断言详情
- JUnit XML 报告转换(兼容 Jenkins / GitLab CI)
- 编写 GitHub Actions workflow 示例:
1
2
3
4
5
6
7- name: Run Lua Tests
run: |
.\RunLuaTests.ps1 -Filter "Smoke" -Report "test_report.json"
- name: Publish Test Report
uses: dorny/test-reporter@v1
with:
path: test_report.json - Lua 测试发现 CLI 工具:扫描
Content/Scripts/Tests/输出测试清单 JSON - 与
Automation_LyraStarterGame.sln的自动化方案并行配置
验收标准:
| 编号 | 验收项 | 验证方式 |
|---|---|---|
| ✅ P5.1 | RunLuaTests.ps1 -Filter "Weapon" -Report "report.json" 可正确执行 |
在 CI 机器上无图形界面执行,ExitCode 正确 |
| ✅ P5.2 | report.json 包含每个测试的 name / status / duration_ms / error_message |
跑 3 个测试(2 Pass + 1 Fail),JSON 数组含 3 个条目 |
| ✅ P5.3 | JUnit XML 格式可用 Jenkins JUnit Plugin 解析 | 在本地 Jenkins 实例导入,显示测试趋势图 |
| ✅ P5.4 | GitHub Actions workflow 示例文件在 .github/workflows/lua-tests.yml 可被 GitHub 识别 |
Push 后 Actions tab 出现 “Lua Tests” workflow |
3.6 Phase 6 — 蓝图 → Lua 迁移与用户手册
目标:制定蓝图测试迁移策略,批量迁移现有 BP FunctionalTest,验证 80/20 目标可行性。
工作项:
- 梳理现有 BP FunctionalTest 清单(ShooterTests 插件 +
Content/FunctionalTests/):- 标注复杂度(简单/中等/复杂)、优先级(高/中/低)
- 识别可自动转换的模式(如 Click + WaitFrames + AssertAmmo)
- 迁移策略(分批实施):
- 第一批(Phase 6 内完成):简单 Smoke 测试 —
B_Test_FireWeapon、FT_WF_01_01_SingleShot - 第二批:中等复杂度 —
WF_01_02_MultiShot、B_Test_AutoRun - 第三批:复杂集成测试 — 动画测试、网络复制测试
- 第一批(Phase 6 内完成):简单 Smoke 测试 —
- 编写《蓝图 → Lua DSL 迁移指南》:
- 常见 BP 节点 → Lua DSL 等价写法速查表
- ECF 节点(WhileTrueExecute / Delay / Coroutine)→ Lua 协程转换
- 迁移检查清单(功能对等、性能不退化、断言一致)
- 编写《LuaMachine Test DSL 用户手册》(
TestDocs/04-用户手册/):- 快速开始(5 分钟上手)
- DSL 语法速查表
- 常用测试模式(武器测试 / 输入测试 / 状态变更测试 / 网络测试)
- FAQ
验收标准:
| 编号 | 验收项 | 验证方式 |
|---|---|---|
| ✅ P6.1 | 现有 BP FunctionalTest 清单梳理完毕,每项标注复杂度和迁移优先级 | 可查看清单表格(CSV/Markdown) |
| ✅ P6.2 | ≥4 个 BP FunctionalTest 有功能等价的 Lua DSL 版本 | 两个版本在相同条件下执行,断言结果一致 |
| ✅ P6.3 | 迁移后代码行数对比:Lua DSL 版本 ≤ BP 版本节点数 1/3(估算) | 统计表格 |
| ✅ P6.4 | 《蓝图 → Lua DSL 迁移指南》含常见 BP 节点等价写法 ≥20 条 | 速查表最少 20 条常见映射 |
| ✅ P6.5 | 《用户手册》覆盖 80% 以上日常测试场景 | 让一个不了解 Lua DSL 的开发者阅读,15 分钟内能写出第一个测试用例 |
3.7 执行模式:PIE vs Automation 双入口
Lua 测试脚本写一次,两种执行模式都可用:
PIE 模式(ULuaFunctionalTest) |
Automation 模式(LuaTests.*) |
|
|---|---|---|
| 触发方式 | 关卡中放置 ULuaFunctionalTest → PIE 运行 → 自动执行 |
Session Frontend / 命令行 / UnrealMCP |
| 视觉反馈 | ✅ 实时看到角色开枪、移动、换弹 | ❌ 无渲染(-NullRHI) |
| 调试体验 | ✅ 可暂停、可单步断言、可截图 | ❌ 仅日志输出 |
| CI 集成 | ❌ 需要 GPU/引擎渲染 | ✅ 命令行无头执行 |
| 适用场景 | 开发期调试、录演示、手工验证 | 回归测试、CI 流水线、批量执行 |
| GameFeature 加载 | ✅ PIE 加载 Experience 时自动激活 | ⚠️ 需代码显式激活 GameFeature 再执行测试 |
同一份 .lua 脚本,两条执行路径:
1 | Content/Scripts/Tests/Weapon/single_shot_spec.lua |
GameFeature 懒加载处理策略:
ShooterTests 及其他 GameFeature 插件默认处于 Registered 状态(不自动激活)。Lua 测试在执行前需要确保依赖的 GameFeature 已加载:
- PIE 模式:
ULuaFunctionalTest放置在 ShooterTests 关卡中时,关卡本身会触发 Experience 加载,GameFeature 自动激活。重写IsReady()等待 GameFeatureActive状态。 - Automation 模式:
FLuaTestAutomationProvider在RunTest()前调用UGameFeaturesSubsystem::LoadAndActivateGameFeaturePlugin()显式激活依赖的 GameFeature。每个TestSuite通过options.depends_on = {'ShooterCore', 'ShooterTests'}声明所依赖的 GameFeature 插件名。 - 超时保护:GameFeature 加载超时(默认 10 秒),超时后跳过依赖该 Feature 的测试并标记为 Skipped。
4. 模块结构
4.1 文件组织
1 | LyraStarterGame/ |
4.2 模块依赖
1 | LuaTestFramework (C++ Plugin) |
5. 关键设计决策
| 决策 | 选择 | 理由 |
|---|---|---|
| 测试编写主力 | Lua DSL(80%) | 替代蓝图:热重载、文本 diff、CI 友好、脱离编辑器编写 |
| C++ 角色 | 底层轮子(20%) | CQTest 负责需要编译期检查的数值/数学验证 |
| DSL 宿主语言 | Luau(LuaMachine 内置) | Lua 5.1 超集,类型注解可选,Roblox 开源,活跃维护 |
| DSL 风格 | Lua table 声明式 | 接近现有测试宪章的描述风格;expect():to_xxx() 链式断言业界通用 |
| 桥接方式 | LuaMachine 手动注册 + ULuaTestBridge 薄封装 |
12 个 API 手动注册 ≈120 行胶水代码;薄封装处理 Latent、类型转换、上下文管理 |
| 流程控制 | Lua 协程 coroutine.yield |
ECF 在 Lua 侧无直接等价物;协程是最自然的异步方案 |
| 测试注册 | UE Automation Framework | 与 CQTest / BP FunctionalTest 统一面板、统一 CI 流程 |
| 模块归属 | 独立 LuaTestFramework 插件 |
遵循 GameFeatures 模式;可选加载;零侵入 LyraGame |
| 测试发现 | 文件系统扫描 + 约定优于配置 | Content/Scripts/Tests/**/*_spec.lua 自动注册,无需手动维护列表 |
6. 风险与缓解
| 风险 | 等级 | 缓解措施 |
|---|---|---|
| LuaMachine 在 UE 5.8 上的 API 兼容性 | 🟢 低 | LuaMachine 采用独立 VM 架构,不侵入 UE 反射系统,对 UE 版本升级不敏感;2025-06 已发版,Phase 1 首轮验证 |
| Luau 方言学习成本 | 🟢 低 | Luau 是 Lua 5.1 的超集(类型注解可选),DSL 语法极简,用户手册 5 分钟上手 |
| 手动注册胶水代码维护成本 | 🟢 低 | LyraTestUtilities 仅 12 个函数,胶水代码量可控(≤200 行);新增 API 时遵循模板即可 |
| Lua 调试体验不如蓝图可视化 | 🟡 中 | 详细断言失败上下文 + Lua 堆栈映射;PIE 模式可实时观察角色行为;后续可考虑 VSCode Luau 调试器集成 |
| Lua VM GC / 生命周期管理 | 🟡 中 | LuaMachine 每个 UObject 实例可持有独立 Lua 状态;测试结束后 VM 随 Actor 销毁自动回收 |
| Lyra 内部类 API 变更导致 Lua 测试断裂 | 🟢 低 | LyraTestUtilities 是 API 稳定层;Lua 只调用 LyraTestUtilities,不直接调用 LyraGame 内部类 |
| GameFeature 懒加载导致 Lua 测试脚本加载时资产不可用 | 🟡 中 | Phase 4 处理:Lua 测试在执行前等待 GameFeature 加载完成;由 ULuaFunctionalTest 的 IsReady 保证 |
| Lua 热重载在运行中的协程行为不确定 | 🟡 中 | Phase 1 充分验证;热重载仅在测试未运行状态下生效;运行中的测试不受影响 |
7. 与 UnrealMCP 的协同
本项目已集成 UnrealMCP v0.9.1(Plugins/UnrealMCP/),其 AutomationTestToolset 可发现和执行 UE Automation 测试。
Lua Test DSL 注册到 Automation Framework 后:
1 | AI Agent (Reasonix/Copilot/Cursor) |
这意味着:所有现有 MCP-based AI 测试工作流无需任何修改即可驱动 Lua 测试。
8. 版本记录
| 日期 | 版本 | 变更内容 |
|---|---|---|
| 2026-07-22 | v1.0 | 初始版本 — 6 阶段实施计划 + 架构设计 + 验收标准(当时基于 UnLua) |
| — 确立 LuaMachine + Lua DSL 方案 | ||
| — 复用 LyraTestUtilities / Automation Framework / UnrealMCP | ||
| 2026-07-22 | v1.1 | 定位修正 — Lua DSL 从「并行选项」升级为「蓝图替代品」 |
| — 明确目标比例:80% Lua DSL + 20% C++ CQTest,蓝图逐步淘汰 | ||
| — Phase 6 扩展为「蓝图→Lua 迁移」含分批策略 + 迁移指南 | ||
| — 新增「蓝图节点→Lua DSL 等价写法」迁移速查表要求 | ||
| 2026-07-22 | v1.2 | 底层引擎切换 — UnLua → LuaMachine(复核发现 UnLua 不兼容 UE 5.8) |
| — Phase 1 重写:LuaMachine 安装 + Luau 方言 + 新验收标准 | ||
| — Phase 3 重写:手动注册替代自动反射,新增 UnLua vs LuaMachine 对比表 | ||
| — 新增 §3.7:PIE vs Automation 双入口 + GameFeature 懒加载处理策略 | ||
| — 风险表全面更新:8 项风险重新评估 | ||
| — 设计决策表更新:Luau 方言、手动注册架构 |
文档结束。配套 API 参考见 16-LuaDSL-API参考。
16 — Lua DSL API 参考
本文档是 Lua Test DSL 重写后的完整 API 参考手册。
新 API 按引擎底层概念组织为 7 个显式子命名空间,而非扁平的 ctx 方法。
原扁平 API(ctx:GetAmmo / ctx:Click 等)已移除,迁移到ctx.input/Helpers/*.lua。
设计文档见 15-UnLuaTestDSL设计方案。
1. 快速开始
1 | -- Content/Scripts/Tests/Weapon/default_weapon_spec.lua |
调用约定:每个 Helper 函数第一个参数为 ctx(TestContext 实例),后续为业务参数。
2. DSL 语法规范
2.1 TestSuite 定义
1 | TestRunner.TestSuite(name:string, options:table?) → TestSuite |
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string |
✅ | 测试套件名称,作为 Automation 注册名的一部分 |
options.tags |
table<string> |
❌ | 标签列表,如 { "weapon", "smoke" } |
options.timeout |
number |
❌ | 每个 test 的超时秒数,默认 30 |
options.before_all |
function(suite, ctx) |
❌ | Suite 级别:所有 test 之前执行一次 |
options.after_all |
function(suite, ctx) |
❌ | Suite 级别:所有 test 之后执行一次 |
Automation 注册名:LuaTests.<name>.<test_method_name>
2.2 Test 定义
1 | function suite:test_<name>(ctx:TestContext) |
- 方法名
test_xxx自动识别为测试用例 - 不以
test_开头的方法不会被当作测试执行 - 所有
test_*方法按声明顺序执行
2.3 生命周期钩子
1 | function suite:before_all(ctx) end -- Suite 开始时执行一次 |
执行顺序:
1 | before_all |
2.4 参数化测试
1 | function suite:test_each_reload(ctx) |
2.5 标签过滤
1 | -- 定义时打标签 |
3. 命名空间 API 完整参考
3.1 ctx.actor — 世界/场景查询与操作
| 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|
GetPC(index?) |
index:number 默认 0 |
APlayerController |
获取第 N 个玩家控制器 |
GetPawn(index?) |
index:number 默认 0 |
APawn |
获取第 N 个玩家 Pawn |
SpawnActor(classPath, transform?) |
classPath:string, transform:table? |
AActor |
在世界中生成 Actor |
FindActor(classPath) |
classPath:string |
AActor? |
查找第一个指定类型的 Actor |
Teleport(actor, location, rotation?) |
actor, location:FVector, rotation:FRotator? |
— | 传送 Actor |
LookAt(target) |
target:AActor |
— | 让 PC 注视 Actor |
LookAtLocation(location) |
location:FVector |
— | 让 PC 注视位置 |
MoveTo(pawn, location) |
pawn:APawn, location:FVector |
— | 命令 Pawn 移动到位置 |
GetActorLocation(actor) |
actor:AActor |
{X,Y,Z} |
获取位置(反射) |
GetActorRotation(actor) |
actor:AActor |
{Pitch,Yaw,Roll} |
获取旋转(反射) |
GetVelocity(actor) |
actor:AActor |
{X,Y,Z} |
获取速度(反射) |
GetActorBounds(actor) |
actor:AActor |
{Origin,BoxExtent} |
获取包围盒(反射) |
LineTrace(from, to) |
from, to:FVector |
HitResult? |
碰撞检测(反射) |
GetAllActorsOfClass(classPath) |
classPath:string |
table<AActor> |
按类查找所有 Actor(反射,当前 stub) |
FindActorByTag(tag) |
tag:string |
AActor? |
按 Tag 查找 Actor(反射) |
GetComponentByClass(actor, className) |
actor, className:string |
UActorComponent? |
按类名字符串查找组件(C++ 桥接) |
GetChildActor(actor, name) |
actor, name:string |
AActor? |
获取指定名称的子 Actor(反射) |
3.2 ctx.ability — GAS 通用操作
| 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|
HasTag(actor, tagName) |
actor, tagName:string |
boolean |
检查 GameplayTag(现有 C++ 桥接) |
CanActivateAbility(name) |
name:string |
boolean |
查询能力是否能激活(反射,stub) |
ActivateAbility(name) |
name:string |
boolean |
主动触发能力(反射,stub) |
GetCooldownRemaining(name) |
name:string |
number |
查询能力冷却剩余时间(反射,stub) |
GetAbilityTags(name) |
name:string |
table |
查询能力标签(stub) |
HasActiveEffect(effectClass) |
effectClass:string |
boolean |
检查活跃 GameplayEffect(反射,简化) |
GetEffectDuration() |
— | number |
查询 Effect 持续时间(stub) |
GetEffectStacks() |
— | number |
查询 Effect 堆叠层数(stub) |
GetAttribute(attributeName) |
attributeName:string |
number? |
泛化 GAS 属性查询(stub,需专用桥接) |
stub标志表示该函数需要进一步 C++ 桥接支持(FGameplayAttribute 等 struct 类型),当前返回 nil/0。
3.3 ctx.state — 数据层通用原语
| 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|
FindComponent(actor, className) |
actor, className:string |
UActorComponent? |
按类名字符串查找组件(C++ 桥接) |
FindComponents(actor, className) |
actor, className:string |
table |
查找所有匹配组件 |
HasComponent(actor, className) |
actor, className:string |
boolean |
检查组件是否存在 |
GetWorldSubsystem(className) |
className:string |
UWorldSubsystem? |
获取世界子系统(反射) |
GetGameState() |
— | AGameState? |
获取 GameState(反射) |
GetPlayerState(playerIndex?) |
playerIndex:number? |
APlayerState? |
获取 PlayerState(反射) |
GetProperty(object, propertyName) |
object, propertyName:string |
any |
反射读取任意 UProperty。支持 10 种类型:int/float/bool/string/FName/FText/UObject/FVector/FRotator/FGameplayTag/TArray |
GetStatTag(owner, tagName) |
owner, tagName:string |
number |
读取 StatTagStack 计数(反射,string→FGameplayTag 自动转换) |
HasStatTag(owner, tagName) |
owner, tagName:string |
boolean |
检查 StatTag 是否存在 |
SetStatTag(owner, tagName, count) |
owner, tagName:string, count:number |
boolean |
设置 StatTagStack 计数(仅 Authority) |
GetInventoryItems(actor) |
actor:AActor |
table<ULyraInventoryItemInstance> |
获取背包物品列表(反射) |
GetItemStatTag(itemInstance, tagName) |
item, tagName:string |
number |
读取物品级 StatTag |
GetItemDefinition(itemInstance) |
item |
ULyraInventoryItemDefinition? |
获取物品定义 |
3.4 ctx.asset — 资产操作
| 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|
LoadClass(path) |
path:string |
UClass? |
按路径加载类(C++ 桥接) |
LoadAsset(path) |
path:string |
UObject? |
按路径加载资产(C++ 桥接,同 LoadClass) |
FindObject(path) |
path:string |
UObject? |
按路径查找已加载对象(反射,stub) |
ResolvePath(shortName) |
shortName:string 如 "WID_Rifle" |
string? |
将简写名解析为完整资产路径(Lua 实现) |
GetDataTableRow(dtPath, rowName) |
dtPath, rowName:string |
table? |
读取 DataTable 行(反射,stub) |
GetCurveTableValue(ctPath, curveName, input) |
ctPath, curveName:string, input:number |
number? |
读取 CurveTable 值(反射,stub) |
AssetExists(path) |
path:string |
boolean |
检查资产是否存在 |
3.5 ctx.input — 输入模拟
| 方法 | 参数 | 说明 |
|---|---|---|
Click(action, value?) |
action:string (如 "IA_Weapon_Fire"), value:FVector? (默认 {X=1,Y=0,Z=0}) |
单次点击:按下 → 2 帧 → 释放 |
Press(action, value?) |
同上 | 按下(Started),不释放 |
Release(action) |
action:string |
释放(Completed) |
Hold(action, value?) |
同 Click | 持续按住,每帧注入 Ongoing,直到调用 Release |
MoveAxis(axis, value) |
axis:string, value:FVector |
模拟轴输入(包装 Click) |
KeyDown(key) |
key:string 如 "LeftMouseButton" |
键盘按键按下(反射) |
KeyUp(key) |
key:string |
键盘按键释放(反射) |
TypeKey(key) |
key:string |
按下再释放 |
MouseMove(dx, dy) |
dx, dy:number |
鼠标增量移动(通过 AddYawInput/AddPitchInput) |
MouseClick(button?) |
button:string? 默认 "LeftMouseButton" |
鼠标点击快捷方式 |
MouseWheel(delta) |
delta:number |
鼠标滚轮(反射,stub) |
ActivateInputMapping(context) |
context:string |
激活输入上下文(stub) |
DeactivateInputMapping(context) |
context:string |
停用输入上下文(stub) |
3.6 ctx.cheat — 作弊与状态覆盖
| 方法 | 参数 | 说明 |
|---|---|---|
GiveWeapon(weaponPath) |
weaponPath:string 如 "WID_Rifle" |
给当前 Hero 装备武器(C++ 桥接) |
SpawnHero() |
— | 生成本地 Hero Pawn(C++ 桥接) |
SetHealth(pc, value) |
pc, value:number |
覆盖血量(反射+FindComponent) |
SetMaxHealth(pc, value) |
pc, value:number |
覆盖最大血量(ConsoleCommand) |
SetAmmo(pc, ammo, reserve?) |
pc, ammo:number, reserve:number? |
设置弹药量(反射+StatTag) |
GrantAbility(abilityClass) |
abilityClass:string |
授予能力(反射+ASC) |
RemoveAbility(abilityClass) |
abilityClass:string |
移除能力 |
ForceActivateAbility(abilityClass) |
abilityClass:string |
强制激活能力 |
ApplyEffect(effectClass, level?) |
effectClass:string, level:number? |
应用 GameplayEffect(ConsoleCommand) |
RemoveEffect(handle) |
handle |
移除 Effect |
ClearAllEffects() |
— | 清除所有 Effect |
AddTag(actor, tag) |
actor, tag:string |
添加 GameplayTag(反射+ASC) |
RemoveTag(actor, tag) |
actor, tag:string |
移除 GameplayTag |
KillActor(actor) |
actor:AActor |
杀死 Actor(反射+HealthComponent) |
ResetHealth(actor) |
actor:AActor |
恢复满血 |
TriggerDeath() |
— | 自杀(ConsoleCommand) |
SetProperty(object, name, value) |
object, name:string, value |
反射写入任意 UProperty |
3.7 ctx.config — 配置
| 方法 | 参数 | 说明 |
|---|---|---|
ConsoleCommand(command) |
command:string |
执行控制台命令(C++ 桥接) |
ActivateGameFeature(pluginName) |
pluginName:string 如 "ShooterCore" |
激活 GameFeature(C++ 桥接) |
SetCVar(name, value) |
name, value:string |
设置控制台变量(ConsoleCommand 包装) |
GetCVar(name) |
name:string |
读取控制台变量(stub) |
ResetCVar(name) |
name:string |
重置控制台变量为默认值 |
GetLocalSettings() |
— | 获取本地设置对象(反射) |
GetSharedSettings(playerIndex?) |
playerIndex:number? |
获取共享设置对象(反射) |
GetDeveloperSettings(className) |
className:string |
获取开发者设置(stub) |
4. 顶层方法(保留在 ctx)
4.1 流程控制
| 方法 | 参数 | 说明 |
|---|---|---|
:Wait(seconds) |
seconds:number |
暂停指定秒数。必须在协程中调用 |
:WaitFrames(n) |
n:number |
暂停 N 帧。必须在协程中调用 |
:WaitUntil(fn, timeout?) |
fn:function→boolean, timeout:number? (默认 10s) |
每帧轮询 fn,fn 返回 true 或超时继续 |
:WaitWhile(fn, timeout?) |
同上,fn 返回 false 时继续 | 与 WaitUntil 逻辑相反 |
:Tick(fn) |
fn:function(deltaTime) |
注册每帧回调,返回取消函数(stub — 当前无操作) |
4.2 断言
所有断言通过 ctx:expect(value) 返回断言对象,链式调用。
1 | ctx:expect(1 + 1):to_equal(2) |
ctx:expect(v, label?) — 可选标签。to_xxx(expected, msg?) — 可选失败消息。
4.3 其他
| 方法 | 参数 | 说明 |
|---|---|---|
:Log(message) |
message:string |
输出日志到 UE Output Log |
:TakeScreenshot(name?) |
name:string? |
截屏 |
5. Helpers 层 API
Helpers 是基于命名空间 API 构建的语义层,提供 Lyra 项目特定的高层查询函数。
5.1 StateHelper
local StateHelper = require("Tests.Helpers.StateHelper")
| 函数 | 说明 |
|---|---|
StateHelper.GetHealth(ctx, pc) |
获取玩家血量(通过 FindComponent→GetProperty) |
StateHelper.GetMaxHealth(ctx, pc) |
获取玩家最大血量 |
StateHelper.IsDead(ctx, pc) |
判断玩家是否死亡(血量 ≤ 0) |
StateHelper.GetCurrentWeapon(ctx, pc) |
获取当前远程武器 Actor |
StateHelper.HasQuickBar(ctx, pc) |
判断 QuickBar 是否已初始化(用于 WaitUntil 轮询) |
StateHelper.HasWeaponReady(ctx, pc) |
判断武器是否就绪(弹药 > 0) |
5.2 WeaponHelper
local WeaponHelper = require("Tests.Helpers.WeaponHelper")
| 函数 | 说明 |
|---|---|
WeaponHelper.GetAmmo(ctx, pc) |
获取当前弹药数(通过 StatTagStack) |
WeaponHelper.GetMagazine(ctx, pc) |
获取弹匣容量 |
WeaponHelper.AmmoChanged(ctx, pc) |
弹药变化边缘检测(预读第一次) |
WeaponHelper.FireUntilEmpty(ctx, actionName) |
循环射击直到空膛,返回最终弹药数 |
5.3 CombatHelper
require("Tests.Helpers.CombatHelper")
| 函数 | 说明 |
|---|---|
IsDeadOrDying(ctx, actor) |
判断角色死亡/正在死亡 |
GetDeathState(ctx, actor) |
获取死亡状态枚举值 |
5.4 TeamHelper
require("Tests.Helpers.TeamHelper")
| 函数 | 说明 |
|---|---|
GetTeamId(ctx, actor) |
获取队伍 ID |
CompareTeams(ctx, a, b) |
比较两队(返回 “Same”/“Different”/“Invalid”) |
GetTeamScore(ctx, teamId) |
获取队伍分数 |
5.5 InventoryHelper
require("Tests.Helpers.InventoryHelper")
| 函数 | 说明 |
|---|---|
HasItem(ctx, pc, itemDefName) |
判断是否有指定物品 |
ItemCount(ctx, pc, itemDefName) |
获取物品数量 |
GetEquippedItem(ctx, pc) |
获取当前装备槽物品实例 |
6. 调用约定速查
1 | -- 获取玩家控制器/Pawn |
7. 类型转换说明
Lua ↔ UE 类型映射
| UE 类型 | Lua 表示 | 说明 |
|---|---|---|
int32/float |
number |
自动转换 |
bool |
boolean |
自动转换 |
FString/FName/FText |
string |
自动转换 |
UObject* |
userdata |
FLuaValue(Object) |
FVector |
{X=number, Y=number, Z=number} |
GetProperty / _Call 自动转换 |
FRotator |
{Pitch=number, Yaw=number, Roll=number} |
自动转换 |
FGameplayTag |
string |
_Call / GetProperty 中自动转换 |
TArray |
table (1-indexed) |
GetProperty 自动转换 |
示例:
1 | local loc = ctx.actor:GetActorLocation(actor) |
8. 内部 API(不对外暴露)
以下函数注册在 Lua 全局中,仅供 TestContext.lua 内部使用,测试代码不得直接调用:
| 函数 | 类型 | 说明 |
|---|---|---|
GetPC(index) / GetPawn(index) |
C++ 桥接 | 从 ctx.actor 转发到 C++ |
SpawnActor / FindActor |
C++ 桥接 | 从 ctx.actor 转发 |
Click / Press / Release / Hold |
C++ 桥接 | 从 ctx.input 转发 |
HasTag |
C++ 桥接 | 从 ctx.ability 转发 |
GiveWeapon / SpawnHero |
C++ 桥接 | 从 ctx.cheat 转发 |
ConsoleCommand / ActivateGameFeature |
C++ 桥接 | 从 ctx.config 转发 |
LoadObject |
C++ 桥接 | 从 ctx.asset 转发 |
FindComponent |
C++ 桥接 | 从 ctx.state 转发 |
_Call(obj, func, {args}) |
反射入口 | 调用任意 UFunction |
GetProperty(obj, name) |
反射入口 | 读任意 UProperty |
SetProperty(obj, name, value) |
反射入口 | 写任意 UProperty |
Log / TakeScreenshot |
C++ 桥接 | 通用工具 |
9. 已知限制与计划
| 功能 | 状态 | 障碍 |
|---|---|---|
_Call 支持 out-parameters |
❌ 限制 | GetAllActorsOfClass 等带输出参数的函数无法工作 |
GetAttribute |
❌ stub | 需要 FGameplayAttribute struct 专用桥接 |
GetDeveloperSettings |
❌ stub | GetDefault 是静态 C++ 函数,非 UFUNCTION |
GetCVar |
❌ stub | 需要 IConsoleManager::FindConsoleVariable 桥接 |
Tick(fn) callback |
❌ stub | 需要真正的 FTickableGameObject 每帧回调 |
| 完整 Ability/Effect 查询 | ⚠️ 简化 | HasActiveEffect 等用反射简化实现,Notable 查询待完善 |
10. 版本记录
| 日期 | 版本 | 变更内容 |
|---|---|---|
| 2026-07-26 | v2.0 | 全面重写 — 7 个命名空间 API + 5 个 Helper 文件 + 4 个反射入口 |
| — 替换所有扁平 API 为命名空间调用 | ||
| — 新增 ctx.state 通用原语层(FindComponent/GetProperty/StatTag/Inventory) | ||
| — 新增 ctx.ability:HasTag/GetCooldownRemaining/HasActiveEffect | ||
| — 新增 ctx.actor:GetActorLocation/LineTrace/GetComponentByClass 等反射函数 | ||
| — 新增 ctx.input:MoveAxis/KeyDown/KeyUp/MouseMove 等扩展输入 | ||
| — 新增 ctx.cheat:SetHealth/GrantAbility/ApplyEffect 等 15 个作弊函数 | ||
| — 新增 ctx.config:SetCVar/Get/Reset + LocalSettings | ||
| — Helpers: 5 个文件 18 个语义函数 | ||
| — 全部 7 个已有测试已迁移到新 API |
文档结束。设计文档见 15-UnLuaTestDSL设计方案。架构决策见 docs/adr/0001-*.md 和 docs/adr/0002-*.md。
17 — Lua 测试框架搭建复盘
状态:复盘完成(2026-08-03)
结论:Lua(LuaMachine)测试框架方案已放弃,测试体系回归 C++ CQTest + 手工测试。
本文档是 15-UnLuaTestDSL设计方案、16-LuaDSL-API参考、docs/adr/0001-*/0002-*的收尾记录,仅作历史参考。
0. 一句话结论
在 LyraStarterGame 中用 Lua 搭建测试框架的尝试,最终于 2026-08 决策窗口终止。放弃的直接原因是:
- 对 Lua DSL 设计缺乏深入调研 —— 没有先弄清”DSL 要隐藏什么、暴露什么、写给谁”,就进入了实现;
- 对抽象层设计没有把握 —— Lua API 层直接连接 C++ 桥接层,没有形成有语义厚度的抽象;
- 测试中的 Lua 脚本混入大量游戏引擎层逻辑 —— 脚本编写者仍需直面 PC / Pawn / ASC / StatTag / QuickBar 等引擎概念,没有做到”简化测试”的初衷。
最终选择:放弃 Lua 方案,直接用 C++(CQTest)编写自动化测试。
1. 背景与目标(当初为什么搭 Lua 测试框架)
蓝图功能测试(AFunctionalTest)在项目迭代中暴露了 5 个痛点(见 15-UnLuaTestDSL设计方案 §1.2):
| 痛点 | 说明 |
|---|---|
| 版本控制不友好 | .uasset 二进制,diff/merge 几乎不可能 |
| 批量修改困难 | 无法文本搜索/替换,重构成本高 |
| CI 文本化报告难 | 蓝图层结果需额外手段导出 |
| 编写效率低 | 多步断言/循环等待的节点连线冗长 |
| 编辑器依赖 | 写测试必须打开 UE 编辑器 |
由此提出 Lua DSL 方案,目标是把测试编写主力从蓝图换成 Lua:80% 测试用 Lua DSL(功能/集成/回归),20% 用 C++ CQTest(底层数值/编译期检查),蓝图退居辅助。核心卖点是热重载、文本 diff、CI 友好。
方案演进:
- 13-测试辅助API设计方案(7/19):C++ 造轮子、蓝图用轮子 → LyraTestUtilities 函数库 + ECF 流程控制
- 15-UnLuaTestDSL设计方案(7/22,v1.0 → v1.2):C++ 造轮子、Lua 用轮子 → LuaMachine 桥接 + DSL Runtime(底层引擎从 UnLua 切换为 LuaMachine,因 UnLua 不兼容 UE 5.8)
2. 实际发生的时间线
| 日期 | 事件 | 产物 / 证据 |
|---|---|---|
| 2026-07-19 | 测试辅助 API 方案 v1.0(蓝图优先) | TestDocs/00-总纲/13-测试辅助API设计方案.md |
| 2026-07-22 | Lua DSL 设计方案 v1.0→v1.2(UnLua → LuaMachine);API 命名空间与混合桥接决策 | 15-*、docs/adr/0001-*、0002-* |
| 2026-07-25 | DSL Runtime 与学习记录:metatable 是命名空间基石 | learning-records/0001-* |
| 2026-07-26 | API 参考 v2.0:7 个命名空间 + 5 个 Helper + 反射入口;7 个测试脚本迁移 | 16-LuaDSL-API参考.md |
| 2026-07-27 | PIE 集成 handoff:发现 Automation 中测试”跑通”但 PIE 从未启动 | .reasonix/handoffs/handoff-lyra-lua-test-dsl-pie-plan-d.md |
| 2026-08-02 | 决策:弃用蓝图功能测试;事件测试改用 CQTest 显式驱动;13 号方案冻结 | 00-测试总纲.md、docs/adr/0003-*、13 号方案头部冻结标记 |
| 2026-08-03 | 测试体系收敛为 C++ CQTest + 手工;Lua 方案正式终止并复盘 | 本文档 |
实际完成量:
Plugins/LuaTestFramework:C++ 桥接层(32 个注册函数)+ Automation 注册 + PIE 入口Content/Scripts/Tests/:DSL Runtime(TestRunner / TestContext / Assertion)+ 7 个测试脚本 + 5 个 Helper- 配套设计文档、ADR、学习记录、运行脚本(
RunLuaTests.bat) - 最终状态:LuaTestFramework 插件源码保留在仓库,但未登记进
LyraStarterGame.uproject的启用插件列表(LuaMachine仍有登记),已不在项目主链路中
3. 放弃原因复盘(核心)
3.1 原因一:对 Lua DSL 设计缺乏深入调研
现象:设计方案在关键问题没有调研结论的情况下直接进入实施:
- 没有回答 DSL 的语义模型:测试语法描述的是”给武器上弹、射击、角色复活”这类游戏行为,还是”调用引擎 API”?
- 没有验证 LuaMachine / Luau 的能力边界(反射、类型、协程、IDE 补全)就写进目标:spec 把”完整 intellisense / IDE 补全”列为验收目标,实际从未落地;
- 风险表把”手动注册胶水代码维护成本”估为 🟢 低(≤200 行),实际每个新 API 都要改 C++、重编译、再同步 Lua 文档与类型转换,维护点远不止代码行数;
- 实施后期才暴露基础问题:API 参考 v2.0(7/26)标注了 6 个 stub(GetAttribute / GetEffectDuration / GetCVar / GetDeveloperSettings / Tick 回调等),次日 PIE 集成 handoff 又发现”测试跑通但 PIE 从未启动”。框架的地基问题是在投入大量工作量之后才被验证的。
教训:DSL 设计属于”前置设计投入占比很高”的工作,不调研语言生态、IDE 生态、执行模型就开工,等于用实现成本去买设计结论。
3.2 原因二:抽象层设计没有把握,Lua API 层直接连接 C++ 桥接层
现象:TestContext.lua 几乎是桥接函数的”别名表”,没有语义厚度:
1 | -- 透传:Lua 命名空间 = C++ 桥接函数换个名字 |
后果:
- 约定为内部 API 的
_Call / GetProperty / SetProperty / FindComponent被各命名空间普遍使用,测试可见路径上到处是字符串函数名、类名字符串; - 引擎概念原样穿过抽象层:魔法数字(
ECC_WorldStatic = 0、IE_Pressed = 1、IE_Axis = 2)、硬编码 Tag("Lyra.ShooterGame.Weapon.MagazineAmmo")、console 命令字符串("cheat SetHealth %f"); - API 表面远大于实现:多个命名空间函数是 stub(返回 0 / nil / 空表),”抽象层”实际是”未完成的桥接层的门面”。
本质:抽象层不是”把 C++ 函数包一层”,而是”把引擎概念消化成领域语义”。这一步没有做,抽象层就等于零。
3.3 原因三:Lua 脚本混入大量引擎层逻辑,未实现”简化测试”的初衷
现象:测试脚本要求编写者具备完整的 UE 引擎知识,复杂度并不比 C++ 低:
single_shot_spec.lua 的 before_each 需要 15+ 行引擎层准备逻辑:
1 | function suite:before_each(ctx) |
Helper 层也没有把抽象下沉到业务语义,而是用引擎概念组合:
1 | -- StateHelper / WeaponHelper 内部: |
结论:编写者需要同时懂 Lua、UE 反射体系、Lyra 内部组件——比直接写 C++ 多了一层翻译成本,还丢了 C++ 的编译期检查。这与弃用蓝图的理由完全同构:”复杂逻辑(输入模拟 + 异步等待 + 状态断言)在蓝图中难以编写和维护”——换成 Lua 并没有解决这个问题,只换了一种写法。
3.4 工程事实(加速决策的最后一根稻草)
- PIE 集成断裂:handoff 文档确认,测试在 Automation 中”跑通”了,但 PIE 全程未启动;
EditorContext下CurrentWorld == nullptr,依赖 World 的桥接函数静默返回假值。测试验证的是 Lua 语法,不是集成行为。 而这正是 Lua 方案最大的卖点(替代蓝图做集成测试)。 - 双语言断层成本:类名/函数名/属性名以字符串形式在运行时才报错;C++ 桥接改一处,Lua 文档、API 参考、类型转换要同步多处;调试需要跨 Lua 协程与 UE Tick 两套执行模型。
- 对比实验:同一时期的 C++ CQTest(
EventSystem_Death.spec.cpp)证明,死亡/重生这类复杂链路(事件捕获、状态轮询、真实伤害管线、重生完整性)在 C++ 下直接可行——引擎类型、编译期检查、调试器都是现成的。
4. 根源分析(为什么会出现这个结果)
| # | 根源 | 说明 |
|---|---|---|
| 1 | 目标不可度量 | “80% Lua””简化测试””CI 友好”没有量化验收标准,无法在早期发现”测试没有变简单”这一事实 |
| 2 | 用换语言解决编写方式问题 | 痛点是框架能力(输入模拟、异步等待、断言、报告),这些 C++ 已有轮子(CQTest + LyraTestUtilities);Lua 没有带来新能力,只带来新断层 |
| 3 | 跳过语义模型设计 | 抽象层的前置条件(DSL 到底表达什么、给谁写、怎么写最省)没做完,直接进入桥接实现 |
| 4 | 先搭框架、后写用例 | 验证路径是”语法跑通”,而不是”测试编写者能否快速写出并维护真实用例”;金样用例(golden tests)没有先行 |
| 5 | 对 UE 测试本质认识不足 | CQTest + FMapTestSpawner 下几乎所有用例都是集成测试,地图加载、异步时序、状态轮询的成本不会因为换了脚本语言而消失;Lua 只增加了翻译层,没有减少任何固有成本 |
5. 做对了什么(保留价值)
这次尝试不是零产出,以下资产被保留并继续生效:
- 测试宪章体系:84 个用例(含子状态)的划分、优先级、质量门禁保留至今,是”测什么”的稳定资产;
- 自动化 vs 手工的划分框架:2026-08 决策固化为”重复性高的用例 → C++ CQTest;需要视觉/手感/听感判断 → 手工”,选择标准是”是否会被反复回归执行”;
- LyraTestUtilities C++ 函数库:输入模拟(Click/Press/Release/Hold)、世界查询、Gameplay 查询(弹药/血量/Tag)从”蓝图优先”转向供 CQTest 直接调用,避免了重复造轮子;
- UE5.8 Automation 生态调研结论:Session Frontend 无 Device 标签页、ClientContext 测试不可被 Automation 发现、Epic 用 Gauntlet 进程外驱动等,这些结论对后续测试工具选型仍有价值;
- Lua 语言学习:metatable / 闭包 / 协程的理解沉淀在
learning-records/0001-*,是有效的个人知识资产; - 决策记录习惯:ADR 流程(0001/0002/0003)与本次复盘本身,证明了”决策留痕”的价值。
6. 如果重来:三条可执行建议
- 先做最小验证,再决定引入新语言。用 3 个”金样用例”(覆盖输入模拟 + 异步等待 + 状态断言)分别用 C++ CQTest 和候选 DSL 各写一遍,对比编写时间、调试体验、维护成本后再立项。本次的结果大概率在立项阶段就会被否决。
- 真要做 DSL,先定义语义模型。DSL 的语法应该描述游戏行为(”给武器上弹””射击””等待弹药变化””角色复活”),而不是引擎 API(GetPC / FindComponent / GetStatTag)。用目标用例倒推语法,先让最少的桥接跑通金样用例,再决定是否扩 API。
- **抽象层必须验收”语义厚度”**。禁止透传和 stub 进入公开 API 表面;以”新测试编写者写出第一个用例的时间与行数、新人是否需要懂引擎概念”作为抽象层是否成立的验收标准。
7. 结论与当前状态
当前测试策略(2026-08 决策,见 00-测试总纲.md)
1 | 仅两种方式: |
框架分工:CQTest(游戏内集成/数值/输入)+ Spec(UI 流程)+ Gauntlet(启动链路/性能)+ 手工。
Lua 框架资产状态
| 资产 | 状态 |
|---|---|
Plugins/LuaTestFramework/(C++ 桥接 + Automation Provider) |
保留源码,未登记进 uproject 启用列表,不再维护 |
Content/Scripts/Tests/(DSL Runtime + 7 个脚本) |
保留作历史参考与 Lua 学习材料,不再新增用例 |
TestDocs/00-总纲/13/15/16 设计文档 |
标记为历史参考(13 已冻结) |
docs/adr/0001/0002 |
历史决策记录,保留 |
learning-records/0001 |
保留(Lua 知识本身仍有价值) |
8. 证据索引
| 证据 | 路径 | 说明 |
|---|---|---|
| 设计目标 80/20 与风险表 | TestDocs/00-总纲/15-UnLuaTestDSL设计方案.md |
当初的目标与”低风险”评估 |
| API 表面与 stub 清单 | TestDocs/00-总纲/16-LuaDSL-API参考.md |
7 命名空间 + 6 个 stub/限制(§9) |
| 混合桥接决策 | docs/adr/0001-*、docs/adr/0002-* |
命名空间与反射桥接的历史决策 |
| API 层透传实现 | Content/Scripts/Tests/Core/TestContext.lua |
_Call 直接裸露、魔法数字、stub |
| 脚本中的引擎层逻辑 | Content/Scripts/Tests/Weapon/single_shot_spec.lua |
before_each 15+ 行引擎准备逻辑 |
| PIE 从未启动的发现 | .reasonix/handoffs/handoff-lyra-lua-test-dsl-pie-plan-d.md |
Automation “跑通”但 World==null |
| 2026-08 决策 | TestDocs/00-总纲/00-测试总纲.md、测试计划.md §8.4 |
CQTest + 手工双方式;弃用蓝图 |
| C++ 路线可行性 | Source/LyraGame/Tests/CQTest/EventSystem_Death.spec.cpp、docs/adr/0003-* |
复杂链路在 CQTest 下直接可行 |
| 插件启用状态 | LyraStarterGame.uproject |
LuaTestFramework 未登记;LuaMachine 仍启用 |
文档结束。关联设计文档:15-UnLuaTestDSL设计方案、16-LuaDSL-API参考、13-测试辅助API设计方案。
Lyra 测试封板审查材料
目的:为封板审查环节提供一份可逐项核对的证据索引。审查人按第 7 节核对点逐项打勾;审查通过后,据此撰写《Lyra 测试总结报告》。
项目:LyraStarterGame(UE 5.8)|审查日期:2026-08-03|审查人:__________
1. 封板口径(审查基线)
- 功能封板(2026-08-03 决定,决策 A):自动化 CQTest 全量通过(96/96)+ 无 🔴 阻塞 Bug 即为封板;性能全量、探索性测试、DT-04 网络、SP 剩余子状态等无法在封板日完成的项目,显式列入《已知缺口 / 封板后 backlog》(第 6 节),不影响封板声明。
- 手工测试 Session 1~3 已完成(2026-08-03)。佐证:
TestDocs/Bug报告/4 份 Bug 报告 +TestDocs/03-实施/14-蓝图功能测试实战-WF-01-01.md。 - 不推上线:本轮全部成果仅本地 commit(2026-08-03)。
2. 自动化证据索引(96/96 全绿,2026-08-03)
| 套件 | 测试文件(Source/LyraGame/Tests/) |
用例数 | 结果 | 运行方式 | 说明 |
|---|---|---|---|---|---|
| WeaponSystem(EQ/WS/WF 数值) | CQTest/WeaponSystem_Equipment.spec.cpp + CQTest/WeaponSystem_Numerics.spec.cpp + CQTest/WeaponSystem_Firing.spec.cpp |
21 | 21/21 | 常驻编辑器 MCP(AutomationTestToolset) | 全量基线回归(02-full-suite-baseline,69/69 批次内) |
| ControlSystem(MV/AM/NV/FR) | CQTest/ControlSystem.spec.cpp |
30 | 30/30 | 同上 | 同上 |
| EventSystem.Death(DT-01/03/05/06/07) | CQTest/EventSystem_Death.spec.cpp(+ LyraEventDeathTestAbility) |
13 | 13/13(65.5s) | 同上 | 冷启动复核 13/13 全绿(01-verify-event-death-cqtest) |
| InitChain(IN-01~05) | InitChain/ShooterMapsInit.spec.cpp |
5 | 5/5 | 同上 | 冷启动复核 5/5 全绿 |
| CameraSystem_Numeric(NV-01/02) | CQTest/CameraSystem_Numeric.spec.cpp |
9 | 9/9(0.3s,纯单元,无 PIE) | 直接执行 | 默认 80 / meta [5,170] / Clamp 契约;俯仰 ±89 / ±89.9(03-camera-numeric-cqtest) |
| AudioSystem_Numeric(NV-01/02 + UI 边界) | CQTest/AudioSystem_Numeric.spec.cpp |
8 | 8/8(0.13s,纯单元,无 PIE) | 直接执行 | 5 通道默认 1.0 / setter clamp [0,1] / SourceRange / 通道独立(04-audio-numeric-cqtest) |
| SpreadSystem(SP-01/02/05/06 单元 + SP-01-02/04 PIE) | CQTest/SpreadSystem_Numeric.spec.cpp + CQTest/SpreadSystem_Functional.spec.cpp |
10 | 10/10 | 单元直接执行 + MCP PIE(L_Expanse) | 原型→正式化(08-sp-cqtest-prototype) |
| 合计 | — | 96 | 96/96 |
运行方式备注:
- 批量回归顺序:Camera → Audio → Spread → Weapon → Control → Event → InitChain;每批前冷启编辑器,规避 L_Expanse socket / DT-06 偶发 flake。
- 封板回归(2026-08-04 已完成):冷启动后全量复跑 96/96 全绿;DT-06/07 偶发失败已根因修复
(双重生 churn 抢占 PlayerState ASC + 死亡能力异步授予,见 19 号审查 A 类处置提交2f0b077),
复跑 3 次隔离 + 套件内均绿;InitChain L_Expanse 5/5 通过。
3. 手工证据索引
| Session | 内容 | 状态 | 佐证 |
|---|---|---|---|
| Session 1 | 武器动画 WA-01 |
✅ 已完成 | Bug 报告:装备动画-武器先显示后播放拿出动作.md、WF-02-04-射击途中拒绝换弹指令(非阻塞).md |
| Session 2 | 控制动画 AN-01 + 视角 LC-01 + 相机 CM-01/02/05 | ✅ 已完成 | Bug 报告:AM-03-05-Dash中操作行为不一致.md |
| Session 3 | HUD HD-01 |
✅ 已完成 | 无新增 Bug |
| 蓝图功能测试 | WF-01-01ShooterTests/Blueprint/weapon/) |
✅ 已完成 | TestDocs/03-实施/14-蓝图功能测试实战-WF-01-01.md(与宪章 CQTest 要求不符,按偏差记录) |
4. 决策与偏差登记(审查重点)
| # | 决策 / 偏差 | 内容 | 出处 | 影响 |
|---|---|---|---|---|
| 1 | WF-01/02 走蓝图功能测试 | 已用蓝图完成,与宪章”CQTest 实现”不符;只记录偏差,不补 CQTest | 2026-08-03 决定 | 测试计划 §4.4、用例分级清单已标注 |
| 2 | 音频音量 clamp = 契约硬化,非 Bug | C++ setter 原无 clamp;补 FMath::Clamp(InVolume, 0, 1) 使”负值→0”契约成立 |
2026-08-03(04-audio-numeric-cqtest) |
生产改动 Settings/LyraSettingsLocal.cpp(5 个 Set*Volume);不另开 Bug 报告 |
| 3 | SP 散布系列:原型验证 → 正式化 | 原型可行即正式推进,10/10 全绿 | 2026-08-03 决定(08-sp-cqtest-prototype) |
剩余子状态进 backlog(§6) |
| 4 | 用例删除 / 合并 | 状态系统整组(2026-08-02);NV-01 队伍数值、CM-03/04、HD-01-06、EQ-04 并入等(2026-08-03) | TestDocs/00-总纲/用例分级清单.md + 各系统宪章 |
宪章同步;删除项均有理由 |
| 5 | DT-04 网络同步暂缓 | 单机环境本轮不做,保留”暂缓但不可删” | 用户(05-dt04-network-scope) |
已知缺口(§6) |
| 6 | 性能 36 实例不可行 | 本机需 3~6 小时,封板日无法全量;建议骨架 + 冒烟验证管线 | 06-perf-test-feasibility + TestDocs/02-专项测试/13-性能测试可行性调研.md |
已知缺口(§6) |
| 7 | DT-06 / L_Expanse 偶发 flake | 会话负载下偶发失败(Until 超时 / socket_send_failure),冷启动后复跑全绿(13/13、5/5) | 地图 Not yet specified | 封板回归需冷启动后复核(§2) |
5. Bug 登记(4 份,无 🔴 阻塞)
| 编号 | 标题 | 等级 | 来源 | 状态 |
|---|---|---|---|---|
| NV-03-02 | GravityScale=0 时 ABP_Mannequin_Base 除零 | 🟡 中(非阻塞 · LogScript Warning) | CQTest(自动化发现的唯一真实产品 Bug) | 已知问题,随封板发布 |
| AM-03-05 | Dash 中操作行为不一致(蹲下排队/切换打断/部分输入无效) | 🟡 严重 | 手工 | 已知问题,随封板发布 |
| WF-02-04 | 射击途中换弹被静默拒绝 | 🟡 严重(非阻塞) | 手工(蓝图功能测试) | 已知问题,随封板发布 |
| 装备动画 | 装备时武器先显示、后播放拿出动作(闪烁) | 🟢 一般 | 手工 | 已知问题,随封板发布 |
等级说明:无 🔴 阻塞 Bug → 满足功能封板条件;🟡 严重项均为”行为不一致 / 交互时序”类,不影响崩溃与数据安全,作为已知问题发布。
6. 已知缺口 / 封板后 backlog
| 项 | 内容 | 依据 |
|---|---|---|
| DT-04 网络同步 | PIE 双开验证 DeathState 复制 / 回滚 | 暂缓决策 |
| 性能测试 | PF-01 |
性能可行性调研 |
| 探索性测试 | 6 轮 × 90 分钟(8/1-8/2 计划) | 执行计划阶段四 |
| SP 剩余子状态 | SP-02-02/03 弹孔实测(统计型)、SP-03-01~06 乘数动态与过渡、SP-04 首发精度(需确认资产 bAllowFirstShotAccuracy)、SP-05-01/02 冷却延迟时序 |
08-sp-cqtest-prototype |
| 前端页面(FE) | FE-02~06 Spec/Gauntlet 覆盖不完整(Elimination 菜单已实现) | 测试计划 §4.4 |
| 环境 flake 防护 | DT-06 移动输入解锁 Until 超时、L_Expanse LogHttpConnection socket_send_failure;冷启动可复跑通过,已记录复跑方法 |
地图 Not yet specified |
CQTest 代码审查报告
审查日期:2026-08-03
审查范围:Source/LyraGame/Tests/CQTest/ 全部测试文件
审查类型:代码质量 + 测试有效性 + 稳定性 + 代码风格
1. 审查摘要
| 类别 | 发现数 | 严重程度 |
|---|---|---|
| 代码质量问题 | 6 | 🟡 中 |
| 测试有效性问题 | 3 | 🟡 中 |
| 潜在稳定性问题 | 3 | 🟡 中 |
| 代码风格问题 | 4 | 🟢 低 |
结论:测试代码整体质量良好,无🔴阻塞问题。发现的问题均为非阻塞,不影响测试执行结果。
2. 代码质量问题
2.1 弱断言(WeaponSystem_Equipment.spec.cpp)
问题描述:断言条件过于宽松,无法有效验证预期行为。
| 行号 | 当前断言 | 问题 | 建议修复 |
|---|---|---|---|
| 280 | ASSERT_THAT(IsTrue(QuickBar->GetActiveSlotIndex() >= -1)); |
ActiveSlotIndex永远不会是-1,断言永远通过 | 改为验证槽位在有效范围内 |
| 330 | ASSERT_THAT(IsTrue(QuickBar->GetActiveSlotIndex() >= -1)); |
同上 | 同上 |
修复建议:
1 | // 改为验证槽位在有效范围内 |
2.2 条件永远为真(ControlSystem.spec.cpp)
问题描述:第744行条件逻辑错误。
1 | // 当前代码(第744行) |
修复建议:
1 | // 改为验证速度在预期范围内 |
3. 测试有效性问题
3.1 EQ_02_04_RapidRepeatedSwitching测试
问题描述:测试未真正验证快速切换的稳定性。
当前测试逻辑:
- 快速切换20次
- 断言
GetActiveSlotIndex() >= -1(永远通过)
改进建议:
1 | // 记录切换前后的槽位状态 |
3.2 EQ_02_05_SwitchCooldownDelay测试
问题描述:测试未验证冷却时间,只验证了两次切换都能成功。
改进建议:如果需要验证冷却时间,应测量两次切换之间的时间间隔。
3.3 NV_05_01_MaxSpeedNegative测试
问题描述:条件逻辑错误导致测试永远通过。
修复建议:见第2.2节。
4. 潜在稳定性问题
4.1 使用Sleep等待异步操作
问题描述:WeaponSystem_Equipment.spec.cpp第394行使用FPlatformProcess::Sleep(1.0f)等待异步操作完成。
1 | // 第394行 |
风险:
- 在不同硬件上表现不一致
- 可能导致测试超时
- 浪费测试执行时间
改进建议:使用CQTest的WaitUntil机制替代Sleep。
4.2 时序依赖测试
问题描述:EventSystem_Death.spec.cpp中的测试依赖特定时序。
1 | // 第501行 |
风险:
- 在不同帧率下表现不一致
- 可能导致偶发失败(Flake)
改进建议:添加超时机制,避免无限等待。
4.3 依赖特定环境
问题描述:所有测试都依赖特定的地图(L_Expanse、L_ShooterTest_DeviceProperties等)。
风险:
- 地图变更可能导致测试失败
- 地图加载失败导致测试失败
改进建议:测试应具有更好的环境独立性。
5. 代码风格问题
5.1 命名规范
问题描述:ControlSystem.spec.cpp第37行成员变量命名不符合UE风格。
1 | // 当前 |
5.2 类型转换
问题描述:WeaponSystem_Numerics.spec.cpp第407行使用C风格类型转换。
1 | // 当前 |
5.3 缩进不一致
问题描述:WeaponSystem_Numerics.spec.cpp第399行和442行缩进不一致。
1 | // 当前(第399行) |
5.4 注释风格
问题描述:部分测试缺少详细的测试目标说明。
建议:每个TEST_METHOD都应包含:
- 目标(验证什么行为)
- 参数(被测参数)
- 有效区间(参数范围)
- 预期(期望结果)
6. 文件审查详情
6.1 WeaponSystem_Equipment.spec.cpp
- 测试用例数:8
- 发现问题:3(弱断言2个,Sleep1个)
6.2 ControlSystem.spec.cpp
- 测试用例数:20
- 发现问题:2(弱断言1个,命名1个)
6.3 WeaponSystem_Firing.spec.cpp
- 测试用例数:1
- 发现问题:0(代码质量良好)
6.4 WeaponSystem_Numerics.spec.cpp
- 测试用例数:7
- 发现问题:2(类型转换1个,缩进1个)
6.5 EventSystem_Death.spec.cpp
- 测试用例数:10
- 发现问题:1(时序依赖)
6.6 CameraSystem_Numeric.spec.cpp
- 测试用例数:9
- 发现问题:0(代码质量良好)
6.7 AudioSystem_Numeric.spec.cpp
- 测试用例数:8
- 发现问题:0(代码质量良好)
6.8 SpreadSystem_Numeric.spec.cpp
- 测试用例数:6
- 发现问题:0(代码质量良好)
6.9 SpreadSystem_Functional.spec.cpp
- 测试用例数:4
- 发现问题:0(代码质量良好)
6.10 InitChain/ShooterMapsInit.spec.cpp
- 测试用例数:5
- 发现问题:0(代码质量良好)
7. 总结
优点
- 测试结构清晰,注释详细
- 使用CQTest框架,测试可维护性好
- 测试覆盖了关键功能点
- 有详细的测试策略说明
需改进
- 弱断言需要增强
- Sleep应替换为WaitUntil
- 代码风格需要统一
- 部分测试有效性需要提升
建议优先级
- 高优先:修复弱断言(第2.1节)
- 中优先:修复条件逻辑错误(第2.2节)
- 低优先:统一代码风格(第5节)
8. 附录
审查文件列表
- Source/LyraGame/Tests/CQTest/WeaponSystem_Equipment.spec.cpp
- Source/LyraGame/Tests/CQTest/WeaponSystem_Numerics.spec.cpp
- Source/LyraGame/Tests/CQTest/WeaponSystem_Firing.spec.cpp
- Source/LyraGame/Tests/CQTest/ControlSystem.spec.cpp
- Source/LyraGame/Tests/CQTest/EventSystem_Death.spec.cpp
- Source/LyraGame/Tests/CQTest/CameraSystem_Numeric.spec.cpp
- Source/LyraGame/Tests/CQTest/AudioSystem_Numeric.spec.cpp
- Source/LyraGame/Tests/CQTest/SpreadSystem_Numeric.spec.cpp
- Source/LyraGame/Tests/CQTest/SpreadSystem_Functional.spec.cpp
- Source/LyraGame/Tests/InitChain/ShooterMapsInit.spec.cpp
Lyra 测试总结报告
日期:2026-08-04 | 项目:LyraStarterGame(UE 5.8)| 封板口径:功能封板(2026-08-03)
前置材料:18-封板审查材料.md(证据索引)、19-CQTest代码审查报告.md(代码审查及 A1~A3 处置)
1. 结论
- 自动化 CQTest 全量 96/96 全绿(7 套件;2026-08-04 冷启动回归复跑确认,含 DT-06/07 竞态修复后验证)
- 无 🔴 阻塞 Bug;4 份已知问题(🟡×3、🟢×1)随封板发布,不阻塞
- 手工测试 Session 1~3 已完成(2026-08-03)
- 允许功能封板;不推上线,成果本地 commit(2026-08-03)
2. 自动化结果(96/96)
| 套件 | 测试文件 | 用例 | 结果 |
|---|---|---|---|
| 武器与装备(EQ/WS/WF 数值) | WeaponSystem_Equipment/Numerics/Firing.spec.cpp |
21 | 21/21 |
| 控制系统(MV/AM/NV/FR) | ControlSystem.spec.cpp |
30 | 30/30 |
| 事件测试(DT-01/03/05/06/07) | EventSystem_Death.spec.cpp |
13 | 13/13 |
| 初始化链路(IN-01~05) | InitChain/ShooterMapsInit.spec.cpp |
5 | 5/5 |
| 相机数值(NV-01/02) | CameraSystem_Numeric.spec.cpp |
9 | 9/9 |
| 音频数值(NV-01/02 + UI) | AudioSystem_Numeric.spec.cpp |
8 | 8/8 |
| 散布系统(SP-01/02/05/06) | SpreadSystem_Numeric/Functional.spec.cpp |
10 | 10/10 |
| 合计 | — | 96 | 96/96 |
3. 手工与视觉验证
- Session 1(武器动画 WA-01
03、装备 EQ-0105):✅ 完成 - Session 2(控制动画 AN-01、视角 LC-01、相机 CM-01/02/05):✅ 完成
- Session 3(HUD HD-01
05、队伍 TM-0105、音频 AU-01~04):✅ 完成 - 蓝图功能测试 WF-01-01
04 / WF-02-0103:✅ 完成(与宪章”CQTest 实现”不符 → 记录偏差,不补 CQTest) - Bug 报告 4 份:装备动画时序、WF-02-04 换弹互斥、AM-03-05 Dash 行为不一致、NV-03-02 动画蓝图除零(CQTest 发现)
4. 测试侧修复与硬化(本周期)
| 项 | 说明 |
|---|---|
| 音频音量 clamp(契约硬化) | C++ setter 补 FMath::Clamp(0,1),使”负值→0”契约成立(非 Bug) |
| 7 个测试侧失败修复 | 过期资产路径(WID_*/GA_Weapon_Fire)、double 属性读取、出生加载竞态、EQ-04 并入 EQ-03 |
| 19 号代码审查 A1~A3 | NV_05_01 恒真等待→帧计数;EQ_02_05 断言与注释对齐;EQ_03 Sleep→轮询 |
| DT-06/07 重生 churn 竞态修复 | 封板回归中发现:双重生 Pawn 抢占 PlayerState ASC + 死亡能力异步授予,导致”血量归零但死亡状态不转换”;加 3 帧稳定/一致性/死亡能力门禁后确定性通过(DT_07 连跑 3 次 + 套件内均绿) |
5. 已知问题(随封板发布,无 🔴 阻塞)
| 编号 | 标题 | 等级 | 来源 |
|---|---|---|---|
| NV-03-02 | GravityScale=0 时 ABP_Mannequin_Base 除零 | 🟡 中(非阻塞 Warning) | CQTest |
| AM-03-05 | Dash 中操作行为不一致 | 🟡 严重 | 手工 |
| WF-02-04 | 射击途中换弹被静默拒绝 | 🟡 严重(非阻塞) | 手工/蓝图 |
| 装备动画 | 装备时武器先显示、后播放拿出动作 | 🟢 一般 | 手工 |
6. 已知缺口 / 封板后 backlog
| 项 | 内容 |
|---|---|
| DT-04 网络同步 | PIE 双开 DeathState 复制(暂缓) |
| 性能测试 | PF-01 |
| 探索性测试 | 6 轮 × 90 分钟 |
| SP 剩余子状态 | SP-02-02/03 弹孔实测、SP-03-01~06 乘数动态、SP-04 首发精度、SP-05-01/02 冷却延迟时序 |
| 前端页面(FE) | FE-02~06 Spec/Gauntlet 覆盖不完整 |
| 环境 flake 防护 | DT-06 移动输入解锁超时、L_Expanse socket_send_failure(冷启动复跑可过;DT-06 竞态已修复,方法留档) |
7. 交付物与记录
- 测试代码:
Source/LyraGame/Tests/CQTest/(12 份 spec + 辅助类) - 生产改动:
Settings/LyraSettingsLocal.cpp(音量 clamp) - 文档:00-测试总纲、测试计划、执行计划、18-封板审查材料、19-CQTest代码审查报告、本报告
- Git:本地 master(不 push);关键提交
4670919(审查材料)、2f0b077(A1~A3 + DT-06/07 修复)
武器与装备系统 — 测试宪章
测试范围
| # | 测试项 | 说明 |
|---|---|---|
| 1 | 武器装备 | 切换动作 + 装备动作,共 5 个用例(含 5 个子状态) |
| 2 | 武器功能 | 射击 + 换弹,共 2 个用例(含 8 个子状态) |
| 3 | 武器数值 | 自动化测试,距离衰减/射速/弹药量/部位伤害,共 4 个用例(含 14 个子状态) |
| 4 | 武器动画 | 装备/换弹/射击动画,共 3 个用例 |
测试计划
| 测试方式 | 适用用例 | 说明 |
|---|---|---|
| 🖐️ 手工测试 | WA-01 ~ WA-03 | 需要玩家实际操作和视觉判断,如切换手感、动画播放是否正确、操作反馈是否正常 |
| 🤖 C++ CQTest | EQ-01 ~ EQ-05, WF-01 ~ WF-02, WS-01 ~ WS-04 | 装备/射击/换弹/数值/边界值需要精确控制和遍历,用 CQTest 实现(2026-08 回归决策,弃用蓝图功能测试) |
详细分配
| 类别 | 手工测试 | C++ CQTest |
|---|---|---|
| 武器装备 (EQ) | 无 | 5 个全部(含 5 个子状态)✅ 已完成 |
| 武器功能 (WF) | WF-01-03 | WF-01-01、WF-01-02、WF-02(含8个子状态) |
| 武器数值 (WS) | 无 | 4 个全部(含 14 个子状态) |
| 武器动画 (WA) | 3 个全部 | 无 |
测试细则
🎯 用例目的图例:🔵 冒烟(环境/链路基线,失败优先怀疑测试环境)|🟢 回归(防回归保护)|⚪ 回归·单元(数值/配置校验,降级为单元级断言,不走 PIE)|🟠 验收(功能可用性/视觉手感,开发手测等价物)|🚫 已并入或删除(见《用例分级清单》)
1. 武器装备(5 个用例,含 5 个子状态)w
切换动作
EQ-01 初始武器状态
| 编号 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|
| EQ-01 | 角色首次进入游戏 | 检查角色当前持有的武器 | 角色持有默认初始武器 | 🔴 高 | 🔵 冒烟(并入进图冒烟) |
EQ-02 武器切换
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| EQ-02-01 | 滚轮轮切武器 | 持有 2 把以上武器 | 使用鼠标滚轮向后滚动 | 切换到下一把武器,前一把收起 | 🔴 高 | 🟢 回归(切换链路) |
| EQ-02-02 | 数字键切换武器 | 持有 3 把武器,分别在槽位 1/2/3 | 依次按数字键 1 / 2 / 3 | 切换到对应槽位的武器 | 🔴 高 | 🟢 回归(切换链路) |
| EQ-02-03 | 切换到空槽位 | 槽位 3 为空 | 切换到槽位 3 | 无反应,保持当前武器 | 🟡 中 | 🟢 回归(边界:切空槽) |
| EQ-02-04 | ⚠️ 重复快速切换 | 持有 2 把武器 | 快速反复按切换键多次 | 每次切换正常,不卡死,不崩溃 | 🟡 中 | 🟢 回归(并发/时序) |
| EQ-02-05 | ⚠️ 切换武器冷却/延迟 | 持有 2 把武器 | ① 切换到武器 B ② 立即尝试切回武器 A | 切换操作有冷却时间,不能无延迟连续切换 | 🟡 中 | 🟢 回归(冷却时序) |
⚠️ EQ-02-04 / EQ-02-05 风险:快速切换时动画未播完可能导致武器模型重叠或状态错乱,测试时关注是否有残留状态
装备动作
| 编号 | 用例名 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| EQ-03 | 装备武器 | 角色靠近地上的武器 | 走到武器旁触发拾取 | 武器进入快捷栏,角色持有武器 | 🔴 高 | 🟢 回归(拾取主链路) |
| EQ-04 | 重复装备同一武器 | 快捷栏中已有该武器 | 再次拾取同一把武器 | 不能重复拾取(或替换为新的堆叠/弹药) | 🟡 中 | 🚫 并入 EQ-03 |
| EQ-05 | 快捷栏已满时拾取 | 3 个槽位全部有武器 | 尝试拾取地上第 4 把武器 | 拾取失败 或 替换当前武器 | 🟡 中 | 🚫 并入 EQ-03 |
代码实现思路:
1 | 环境: FMapTestSpawner(L_Expanse) → QuickBarComponent / EquipmentManagerComponent |
2. 武器功能(2 个用例,含 8 个子状态)
此部分宪章决策为 C++ CQTest 实现(2026-08 回归决策:弃用蓝图功能测试)。输入模拟用
InputTestActions(按下/按住/松开)。📌 实际执行(2026-08-03):WF 全组已用蓝图功能测试完成(
ShooterTests/Content/Blueprint/weapon/:WF_01_01_SingleShot / WF_01_02_MultiShot / WF_01_03_ShotMove / WF-01-04 / WF-02-01 / WF-02-02 / WF-02-03 + 对应地图)。与宪章 CQTest 决策不符,先按蓝图执行(记录偏差,暂不迁移)。CQTest 侧仅WeaponSystem_Firing.spec.cpp(WF-01-04 自动开火/死亡停止)已实现,与蓝图 WF-01-04 重复,作为补充保留。
射击测试
WF-01 射击
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| WF-01-01 | 单发武器射击 | 持有单发武器,有弹药 | 按射击键 1 次 | 射出 1 颗子弹,弹药 -1 | 🔴 高 | 🔵 冒烟 + 🟢 回归(弹药链路) |
| WF-01-02 | 连发武器射击 | 持有连发武器,有弹药 | 按住射击键不放 | 持续射出子弹,直到松手或弹药耗尽 | 🔴 高 | 🚫 并入 WS-03 |
| WF-01-03 | 射击时角色移动 | 持有武器,有弹药 | ① 跑动射击 ② 跳跃射击 | 移动中射击功能正常,散布比站立更大 | 🟡 中 | 🟢 回归(低优先;散布并入 SP-03) |
| WF-01-04 | ⚠️ 射击途中被打断 | 正在执行射击动作 | 死亡 | 射击被打断 | 🟡 中 | 🚫 并入 DT-01-04 |
换弹测试
WF-02 换弹
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| WF-02-01 | 自动换弹 | 弹药为 0 | 按射击键 | 不能射击,自动触发换弹动作 | 🔴 高 | 🟢 回归(自动换弹路径) |
| WF-02-02 | 主动换弹 | 弹药未满 | 按换弹键 | 换弹动作播放,弹药恢复满 | 🔴 高 | 🟢 回归(换弹主路径) |
| WF-02-03 | 满弹匣时主动换弹 | 弹药全满 | 按换弹键 | 不应有弹药变化事件触发,弹药数量不变 | 🟡 中 | 🚫 并入 WF-02-02 |
| WF-02-04 | ⚠️ 射击途中换弹 | 正在执行射击动作 | 在换弹过程中按换弹键 | 射击被打断执行换弹 | 🔴 高 | 🖐️ 手工补验(2026-08-03 决策:蓝图未覆盖;互斥时序,与 -05 合一例) |
| WF-02-05 | ⚠️ 换弹途中射击 | 正在执行换弹动作 | 换弹过程中被射击指令 | 换弹被打断,弹药不恢复,换弹动画停止 | 🟡 中 | 🚫 并入 WF-02-04(手工补验) |
⚠️ WF-02-01 / WF-02-04 / WF-02-05 风险:弹药耗尽+换弹+切换武器+换弹中断的并发操作可能造成弹药状态不一致。建议额外验证:换弹中切换武器 → 换弹是否取消、弹药是否回满
代码实现思路(文件:Source/LyraGame/Tests/CQTest/WeaponSystem_Fire.spec.cpp):
1 | 环境: FMapTestSpawner(L_Expanse) → QuickBarComponent / 武器实例 |
🔧 2026-08-03 实际落点:上述思路由蓝图功能测试落地(资产见 §2 顶部注记)。WF-02-04/05 互斥用例在资产中没有独立蓝图(WF-01-04.umap 内仅有 WF-02-04_C 残留引用),已决策:手工补验(2026-08-03),执行清单见《测试计划》§5,结果记录到 Bug报告/ 或本表。
3. 武器数值(4 个用例,含 15 个子状态)— 自动化测试
测试方法:边界值分析。对每个参数定义有效区间 [a, b],从通用测试集 B={a-ε, a, a+ε, (a+b)/2, b-ε, b, b+ε} 中按实际风险选择性选取,重点覆盖除零/符号反转/数值溢出风险点。
WS-01 距离衰减参数验证(有效区间 [0, 1])
| 编号 | 细分状态 | 配置距离衰减系数 | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| WS-01-01 | ⚠️ a-ε 低于最小 | -0.01 | 在远近距离分别射击目标 | 衰减不应反向放大伤害 | 负系数→距离越远伤害越高 | 🟡 中 | ⚪ 回归·单元 |
| WS-01-02 | a 最小值 | 0 | 在远近距离分别射击目标 | 远近距离伤害相同(无衰减) | — | 🟡 中 | ⚪ 回归·单元 |
| WS-01-03 | (a+b)/2 中间值 | 0.5 | 在远近距离分别射击目标 | 远距离伤害低于近距离 | — | 🔴 高 | ⚪ 回归·单元(可选 1 个集成冒烟) |
| WS-01-04 | b 最大值 | 1.0 | 在远近距离分别射击目标 | 远距离伤害大幅降低(最大衰减) | — | 🔴 高 | ⚪ 回归·单元(可选 1 个集成冒烟) |
WS-02 射速参数验证(有效区间 [0, 1.0],项目 fireDelayTimeSecs=0.1)
射速由 GA_Weapon_Fire 的 fireDelayTimeSecs 控制(两次射击间延迟秒数),项目默认 0.1s(600 RPM)。
此外 autoRate=1 启用全自动连射。Heat 系统不限制射速,仅控制散布与过热(见事件测试 SP)。
| 编号 | 细分状态 | 配置 fireDelayTimeSecs | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| WS-02-01 | ⚠️ a-ε 低于最小 | -0.01 | 按射击键 | 不崩溃,延迟被 clamp 到 0 | 负值导致 Timer 异常 | 🟡 中 | ⚪ 回归·单元 |
| WS-02-02 | a 最小值 | 0 | 按射击键 | 无延迟,每帧触发射击 | ⚠️ 帧率风险:delay=0 时射速=帧率,60FPS=60发/秒而120FPS=120发/秒,高帧率玩家获得双倍射速优势 | 🔴 高 | ⚪ 回归·单元 |
| WS-02-03 | (a+b)/2 中间值 | 0.5 | 按射击键 | 每 0.5 秒射一发(120 RPM) | — | 🔴 高 | ⚪ 回归·单元 |
| WS-02-04 | b 最大值 | 1.0 | 按射击键 | 每 1.0 秒射一发(60 RPM) | — | 🔴 高 | ⚪ 回归·单元 |
⚠️ 帧率风险:UE 计时器基于帧 Tick,
fireDelayTimeSecs=0时射速完全等于帧率。在可变帧率环境下(20~144 FPS),相同配置下实际 RPM 可差 7 倍以上。建议锁定最小帧率(bUseFixedFrameRate)或将射速逻辑改为基于DeltaTime累积而非帧计数。
WS-03 弹药量参数验证(以步枪标配 30 发弹匣为例,有效区间 [0, 30])
| 编号 | 细分状态 | 配置弹匣容量 | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| WS-03-01 | a 最小值 | 0 | 拾取武器后尝试射击 | 不能射击,触发换弹或空仓提示 | 除零:弹药为0导致UI/逻辑除零 | 🔴 高 | ⚪ 回归·单元 |
| WS-03-02 | (a+b)/2 中间值 | 15 | 连续射击至弹匣打空 | 弹药 -1 递减,到 0 时触发空仓/换弹 | — | 🔴 高 | ⚪ 回归·单元(递减断言并入 WF-01-01) |
| WS-03-03 | b 最大值 | 30 | 连续射击至弹匣打空 | 弹药从满值递减至 0 | — | 🔴 高 | ⚪ 回归·单元(递减断言并入 WF-01-01) |
WS-04 部位伤害倍率验证(有效区间 [0, 10])
| 编号 | 细分状态 | 配置倍率 | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| WS-04-01 | ⚠️ a-ε 低于最小 | -1.0 | 分别射击敌人头部/身体/四肢 | 不崩溃,伤害不应为负(不倒扣血) | 负倍率→击中回血 | 🔴 高 | ⚪ 回归·单元 |
| WS-04-02 | a 最小值 | 0 | 分别射击敌人头部/身体/四肢 | 对应部位伤害为 0 | — | 🟡 中 | ⚪ 回归·单元 |
| WS-04-03 | (a+b)/2 中间值 | 2.0 (头部) / 1.0 (身体) / 1.0 (四肢) | 分别射击各部位 | 爆头伤害为身体2倍,四肢等于身体 | — | 🔴 高 | 🟢 回归(集成:伤害链路) |
| WS-04-04 | b 最大值 | 10.0 | 射击敌人头部 | 爆头伤害为身体10倍 | 数值溢出→单发致死异常 | 🟡 中 | ⚪ 回归·单元 |
代码实现思路:
1 | 环境: FMapTestSpawner(L_Expanse) / LoadObject 读 DataAsset |
4. 武器动画(3 个用例)
| 编号 | 用例名 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| WA-01 | 装备武器动画 | 当前为默认武器 | 拾取或切换到武器 | 切换/装备动画播放,角色正确持枪 | 🟡 中 | 🟠 验收(视觉,手工) |
| WA-02 | ⚠️ 换弹动画 | 弹药未满 | 触发换弹 | 换弹动画正确播放,与武器类型匹配 | 🟡 中 | 🟠 验收(视觉,手工) |
| WA-03 | 射击动画 | 持有武器 | 射击 | 枪口火焰和后坐力动画正常播放 | 🟡 中 | 🟠 验收(视觉,手工) |
⚠️ WA-02 风险:换弹动画与实际弹药恢复可能不同步(动画播完但弹药未恢复,或弹药恢复但动画未播完),测试时关注操作时机的准确性
控制系统 — 测试宪章
测试范围
| # | 测试项 | 说明 |
|---|---|---|
| 1 | 基础移动 🤖 | 方向移动 + 停止 + 转向,共 3 个用例(含 7 个子状态) |
| 2 | 高阶移动 🤖/🖐️ | 跳跃 + 蹲下 CQTest;Dash + 矮通道 手工,共 3 个用例(含 16 个子状态) |
| 3 | 视角控制 🖐️ | 手工 — 鼠标/手柄视角、灵敏度,共 1 个用例(含 4 个子状态) |
| 4 | 动画测试 🖐️ | 手工 — 移动/跳跃/蹲下/Dash 动画表现,共 1 个用例(含 5 个子状态) |
| 5 | 数值测试 🤖/🖐️ | 边界值分析:CQTest 覆盖跳跃速度/重力/MaxWalkSpeed;手工覆盖 Dash 冷却/灵敏度 |
测试计划
| 测试方式 | 适用用例 | 说明 |
|---|---|---|
| 🖐️ 手工测试 | LC-01, AN-01, AM-02-05, AM-03, NV-02, NV-04 | 视角手感、动画表现、Dash 手感/冷却、灵敏度需玩家判断(Dash 为蓝图 GA,自动化成本高) |
| 🤖 C++ CQTest | MV-01 ~ MV-03, AM-01, AM-02(-01~-04), NV-01, NV-03, NV-05, FR-01 ~ FR-02 | 输入模拟 + 组件精确验证 + 数值边界(2026-08 回归决策,弃用蓝图功能测试) |
详细分配
| 类别 | 手工测试 | C++ CQTest |
|---|---|---|
| 基础移动 (MV) | 无 | 3 个全部(含 7 个子状态) |
| 高阶移动 (AM) | AM-02-05(矮通道)、AM-03 全部(6 个子状态) | AM-01 全部(含 5 个子状态)、AM-02 的 -01~-04 |
| 视角控制 (LC) | 1 个全部(含 4 个子状态) | 无 |
| 动画测试 (AN) | 1 个全部(含 5 个子状态) | 无 |
| 数值测试 (NV) | NV-02(Dash 冷却)、NV-04(灵敏度) | NV-01、NV-03、NV-05(MaxWalkSpeed 回填) |
| 死亡冻结 (FR) | 无 | FR-01、FR-02(自动化补充,见 §2 注记) |
🎯 用例目的图例:🔵 冒烟(环境/链路基线,失败优先怀疑测试环境)|🟢 回归(防回归保护)|⚪ 回归·单元(数值/配置校验,降级为单元级断言,不走 PIE)|🟠 验收(功能可用性/视觉手感,开发手测等价物)|🚫 已并入或删除(见《用例分级清单》)
1. 基础移动(3 个用例,含 7 个子状态)
MV-01 方向移动
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| MV-01-01 | 向前移动 | 角色正常站立 | 按 W / 上方向键 | 角色向前移动 | 🔴 高 | 🔵 冒烟 + 🟢 回归(四方向循环实现) |
| MV-01-02 | 向后移动 | 角色正常站立 | 按 S / 下方向键 | 角色向后移动 | 🔴 高 | 🔵 冒烟 + 🟢 回归(四方向循环实现) |
| MV-01-03 | 向左平移 | 角色正常站立 | 按 A / 左方向键 | 角色向左平移 | 🔴 高 | 🔵 冒烟 + 🟢 回归(四方向循环实现) |
| MV-01-04 | 向右平移 | 角色正常站立 | 按 D / 右方向键 | 角色向右平移 | 🔴 高 | 🔵 冒烟 + 🟢 回归(四方向循环实现) |
| MV-01-05 | 斜向移动 | 角色正常站立 | 同时按住 W + D | 角色向右前方移动 | 🟡 中 | 🟢 回归(斜向) |
MV-02 ⚠️ 停止移动
| 编号 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|
| MV-02 | 角色正在移动 | 松开所有方向键 | 角色停止移动 | 🟡 中 | 🚫 并入 MV-01 |
MV-03 移动中转向
| 编号 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|
| MV-03 | 角色正在移动 | 移动时旋转视角 | 角色跟随视角方向转向 | 🟡 中 | 🟢 回归(转向链路) |
⚠️ MV-02 风险:快速松开方向键后立即反方向移动,可能造成停止距离异常或角色滑行
2. 高阶移动(3 个用例,含 16 个子状态)
跳跃、蹲下、Dash 作为统一的”高阶移动方式”管理,死亡时应全部冻结。
🤖 死亡冻结由 CQTest 补充覆盖:
FR-01(死亡后无法移动)、FR-02(复活后恢复移动),对应Gameplay.MovementStoppedTag 的添加/移除。
AM-01 跳跃
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| AM-01-01 | 平地跳跃 | 角色站在地面上 | 按跳跃键 | 角色跳起,落地后动作结束 | 🔴 高 | 🔵 冒烟 + 🟢 回归(承载 NV-01 跳高断言) |
| AM-01-02 | 移动中跳跃 | 角色正在向前移动 | 移动中按跳跃键 | 角色在移动中跳起,保持水平速度 | 🔴 高 | 🟢 回归 |
| AM-01-03 | 蹲跳 | 角色蹲下中 | 按跳跃键 | 角色从蹲伏状态跳起(Lyra 允许) | 🟡 中 | 🚫 删除(与 AM-02-04 重复) |
| AM-01-04 | ⚠️ 连续跳跃 | 角色落地后 | 落地瞬间再按跳跃键 | 可以连续跳跃 | 🟡 中 | 🟢 回归(落地时机边界) |
| AM-01-05 | 空中移动 | 角色在空中 | 跳跃后在空中使用方向键 | 空中可以控制移动方向 | 🟡 中 | 🟢 回归(空中控制) |
⚠️ AM-01-04 风险:连续跳跃的时机判定可能不准,落地瞬间按跳跃可能不触发
AM-02 蹲下
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| AM-02-01 | 蹲下 | 角色正常站立 | 按蹲下键 | 角色进入蹲伏状态,碰撞体高度降低 | 🔴 高 | 🟢 回归(碰撞高度) |
| AM-02-02 | 站起 | 角色蹲伏中 | 按蹲下键 | 角色站起,恢复正常碰撞高度 | 🔴 高 | 🟢 回归(碰撞高度) |
| AM-02-03 | 蹲下行进 | 角色蹲伏中 | 按方向键移动 | 角色以蹲伏姿态移动(速度受限) | 🟡 中 | 🟢 回归(速度受限) |
| AM-02-04 | ⚠️ 蹲跳 | 角色蹲伏中 | 按跳跃键 | 角色从蹲伏状态跳起 | 🟡 中 | 🟢 回归(吸收 AM-01-03) |
| AM-02-05 | 🖐️ 蹲下穿过矮通道 | 前方有矮通道 | 在矮通道前蹲下并前进 | 角色通过矮通道 | 🟡 中 | 🟠 验收(几何/视觉,手工) |
⚠️ AM-02-04 风险:Lyra 允许蹲跳但站起时可能卡在矮空间中,碰撞检测可能异常
AM-03 Dash 🖐️ 手工测试(2026-08 决策:Dash 为蓝图 GA,自动化成本高,转手工)
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| AM-03-01 | 向前 Dash | 角色正常站立 | 按 Dash 键 | 角色朝面向方向快速冲刺一段距离 | 🔴 高 | 🟠 验收(手感,已决策手工) |
| AM-03-02 | 方向 Dash | 角色正在移动 | 移动中按 Dash 键 | 角色朝移动方向冲刺 | 🔴 高 | 🟠 验收(手感,已决策手工) |
| AM-03-03 | ⚠️ Dash 冷却 | 使用 Dash 后 | 立即再次按 Dash 键 | Dash 不能使用,在冷却中 | 🔴 高 | 🟠 验收(冷却手感,已决策手工) |
| AM-03-04 | 冷却结束可再次使用 | Dash 冷却中 | 等待冷却时间结束 | Dash 可在冷却结束后再次使用 | 🟡 中 | 🟠 验收(已决策手工) |
| AM-03-05 | ⚠️ Dash 中操作 | Dash 过程中 | Dash 过程中按跳跃/方向键/近战/手雷/蹲下/射击 | Dash 过程中不能进行其他操作(或可提前取消) | 🟡 中 | 🟠 验收(已决策手工) |
| AM-03-06 | 无移动时 Dash | 角色静止 | 不按方向键,按 Dash 键 | 角色朝当前面向方向 Dash | 🟡 中 | 🟠 验收(已决策手工) |
3. 视角控制 🖐️ 手工测试(1 个用例,含 4 个子状态)
LC-01 视角控制
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| LC-01-01 | 鼠标水平视角 | 角色正常 | 左右移动鼠标 | 角色水平旋转视角 | 🔴 高 | 🟠 验收(手感,手工) |
| LC-01-02 | 鼠标垂直视角 | 角色正常 | 上下移动鼠标 | 角色垂直调整视角 | 🔴 高 | 🟠 验收(手感,手工) |
| LC-01-03 | 手柄摇杆视角 | 使用手柄(xbox) | 推动右摇杆 | 角色视角按照摇杆方向旋转 | 🟡 中 | 🟠 验收(手感,手工) |
| LC-01-05 | ⚠️ 灵敏度调节 | 设置中调整视角灵敏度 | 以相同幅度移动鼠标 | 高灵敏度时视角转动幅度更大 | 🟡 中 | 🟠 验收(灵敏度手感,手工) |
📌 LC-01-04 视角反转已移除:当前游戏版本未提供”反转 Y 轴”设置,无测试入口。若后续版本加入该设置,需恢复此用例。
⚠️ LC-05 风险:灵敏度为 0 或负值时视角无法转动,灵敏度极高时可能导致视角飞转
⚠️ LC-03 / LC-02 风险(死区 & 视角上下限):手柄摇杆死区设置过大会导致轻微推动无响应,死区过小会导致视角漂移。需关注不同手柄(Xbox/PS/第三方)的死区表现差异
⚠️ LC-02 风险(视角顶部/底部映射):垂直视角到达天花板(仰视极限)或地板(俯视极限)时,继续推动可能发生视角翻转或卡死,需关注上下视角限幅是否正确
4. 动画测试 🖐️ 手工测试(1 个用例,含 5 个子状态)
AN-01 移动动画验证
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| AN-01-01 | 移动动画 | 角色正常 | 前后左右移动 | 移动动画自然,与速度匹配 | 🟡 中 | 🟠 验收(视觉,手工) |
| AN-01-02 | 跳跃动画 | 角色在地面 | 按跳跃键 | 跳跃起跳/空中/落地动画完整播放 | 🟡 中 | 🟠 验收(视觉,手工) |
| AN-01-03 | ⚠️ 蹲下动画 | 角色正常 | 蹲下/站起 | 蹲下过渡动画流畅 | 🟡 中 | 🟠 验收(视觉,手工) |
| AN-01-04 | Dash 动画 | 角色正常 | 按 Dash 键 | Dash 动作动画与冲刺效果匹配 | 🟡 中 | 🟠 验收(视觉,手工) |
| AN-01-05 | 移动状态切换动画 | 角色在移动中 | 走路→跳跃→蹲下→dash循环切换 | 动画过渡自然,不卡顿/瞬移 | 🟡 中 | 🟠 验收(视觉,手工) |
⚠️ AN-01-03 风险:快速蹲站可能导致动画与碰撞体状态不同步,角色可能卡在地形中
5. 数值测试 🤖/🖐️ 边界值分析(5 个参数组,含 19 个子状态)
测试方法:边界值分析。对每个参数定义有效区间 [a, b],从通用边界测试集
B = { a-ε, a, a+ε, (a+b)/2, b-ε, b, b+ε } 中按实际风险选择性选取,
重点覆盖除零错误、数值溢出、符号反转等真实风险点。
注:部分参数(如 Dash 冷却)在蓝图中配置,需参照对应 GameplayEffect 资产确认项目实际值。
NV-01 跳跃速度参数验证(有效区间 [0, 420],项目默认值)
| 编号 | 细分状态 | 配置 JumpZVelocity | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| NV-01-01 | ⚠️ a-ε 低于最小 | -0.01 | 按跳跃键 | 不崩溃,角色不应向下弹射 | 负值未过滤→反向跳跃 | 🟡 中 | ⚪ 回归·单元(clamp 校验) |
| NV-01-02 | a 最小值 | 0 | 按跳跃键 | 角色跳不起来(或轻微离地) | 除零:若用作除数则崩溃 | 🔴 高 | ⚪ 回归·单元(clamp 校验) |
| NV-01-03 | (a+b)/2 中间值 | 210 | 按跳跃键 | 角色跳到正常高度(≈ 默认值 420 的一半) | — | 🔴 高 | 🚫 并入 AM-01(跳高实测) |
| NV-01-04 | b 最大值 | 420 | 按跳跃键 | 角色跳到最大设定高度(项目默认值) | — | 🔴 高 | 🚫 并入 AM-01(跳高实测) |
NV-02 Dash 冷却参数验证 🖐️ 手工测试(有效区间 [0, 30],项目值见 GE_HeroDash_Cooldown)
| 编号 | 细分状态 | 配置冷却时间 | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| NV-02-01 | a 最小值 | 0 | 连续使用 Dash | Dash 可无冷却连续使用 | 除零:冷却→0 可能除零崩溃 | 🔴 高 | 🟠 验收(已决策手工) |
| NV-02-02 | (a+b)/2 中间值 | 15 | 使用 Dash 后等待冷却 | 等待约 15 秒后可再次使用 | — | 🔴 高 | 🟠 验收(已决策手工) |
| NV-02-03 | b 最大值 | 30 | 使用 Dash 后等待冷却 | 等待 30 秒后可再次使用 | — | 🔴 高 | 🟠 验收(已决策手工) |
NV-03 重力参数验证(有效区间 [0, 10])
| 编号 | 细分状态 | 配置 GravityScale | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| NV-03-01 | ⚠️ a-ε 低于最小 | -0.01 | 跳跃后观察 | 不崩溃,角色可能向上飘 | 负重力→角色升空 | 🟡 中 | ⚪ 回归·单元(clamp 校验) |
| NV-03-02 | a 最小值 | 0 | 跳跃后观察 | 角色浮空不下落 | 除零:物理引擎除零崩溃 | 🔴 高 | ⚪ 回归·单元(clamp 校验) |
| NV-03-03 | (a+b)/2 中间值 | 5 | 跳跃后观察 | 角色快速下落 | — | 🔴 高 | 🚫 并入 AM-01(下落实测) |
| NV-03-04 | b 最大值 | 10 | 跳跃后观察 | 角色以最大重力下落 | — | 🔴 高 | 🚫 并入 AM-01(下落实测) |
NV-04 视角灵敏度参数验证 🖐️ 手工测试(有效区间 [0.01, 5.0])
| 编号 | 细分状态 | 配置灵敏度 | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| NV-04-01 | ⚠️ a-ε 低于最小 | 0.0 | 移动鼠标 | 不崩溃,视角不能转动 | 零灵敏度→视角卡死 | 🟡 中 | 🟠 验收(手感,手工) |
| NV-04-02 | a 最小值 | 0.01 | 移动鼠标 | 视角极缓慢旋转 | — | 🟡 中 | 🟠 验收(手感,手工) |
| NV-04-03 | (a+b)/2 中间值 | 2.505 | 移动鼠标 | 视角正常跟随鼠标旋转 | — | 🔴 高 | 🟠 验收(手感,手工) |
| NV-04-04 | b 最大值 | 5.0 | 移动鼠标 | 视角以最大速度旋转 | — | 🔴 高 | 🟠 验收(手感,手工) |
NV-05 MaxWalkSpeed 参数验证 🤖 CQTest(有效区间 [0, 600],项目默认值)
| 编号 | 细分状态 | 配置 MaxWalkSpeed | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| NV-05-01 | ⚠️ a-ε 低于最小 | -1.0 | 按方向键 | 不崩溃,角色不应反向奔跑 | 负速度→方向反转 | 🟡 中 | ⚪ 回归·单元(clamp 校验) |
| NV-05-02 | a 最小值 | 0 | 按方向键 | 角色原地不动 | 除零:物理除零崩溃 | 🔴 高 | ⚪ 回归·单元(clamp 校验) |
| NV-05-03 | (a+b)/2 中间值 | 300 | 按方向键 | 角色以半速移动 | — | 🟡 中 | 🚫 并入 MV-01(半速实测) |
| NV-05-04 | b 最大值 | 600 | 按方向键 | 角色全速移动(项目默认值) | — | 🔴 高 | 🚫 并入 MV-01(全速实测) |
代码实现思路:
1 | 环境: FMapTestSpawner(L_Expanse) → CharacterMovement / ULyraSettingsShared |
相机系统 — 测试宪章
测试范围
| # | 测试项 | 说明 |
|---|---|---|
| 1 | 第三人称跟随 🖐️ | 相机跟随 + 旋转 + 环绕,共 1 个用例(含 5 个子状态) |
| 2 | 碰撞避免 🖐️ | 贴墙推近 + 恢复 + 侧向 + 墙角,共 1 个用例(含 4 个子状态) |
| 3 | FOV 与变焦 🚫 | 已删(2026-08-03:游戏无可调 FOV) |
| 4 | 模式切换与过渡 🚫 | 已删(2026-08-03:游戏无相机切换) |
| 5 | 蹲下偏移 🖐️ | 蹲下/站起相机高度,共 1 个用例(含 2 个子状态) |
| 6 | 数值测试 🤖 | FOV / 俯仰限幅 边界值,共 2 个用例(含 9 个子状态) |
测试计划
| 测试方式 | 适用用例 | 说明 |
|---|---|---|
| 🖐️ 手工测试 | CM-01/02、CM-05 | 相机的平滑度、碰撞感受需要视觉判断 |
| 🤖 C++ CQTest | NV-01 ~ NV-02 | FOV/俯仰限幅的边界值 |
详细分配
| 类别 | 手工测试 | C++ CQTest |
|---|---|---|
| 第三人称跟随 (CM-01) | 1 个全部(含 5 个子状态) | 无 |
| 碰撞避免 (CM-02) | 1 个全部(含 4 个子状态) | 无 |
| FOV 与变焦 (CM-03) | 🚫 已删(2026-08-03) | — |
| 模式切换 (CM-04) | 🚫 已删(2026-08-03) | — |
| 蹲下偏移 (CM-05) | 1 个全部(含 2 个子状态) | 无 |
| 数值测试 (NV) | 无 | 2 个全部(含 9 个子状态) |
🎯 用例目的图例:🔵 冒烟(环境/链路基线,失败优先怀疑测试环境)|🟢 回归(防回归保护)|⚪ 回归·单元(数值/配置校验,降级为单元级断言,不走 PIE)|🟠 验收(功能可用性/视觉手感,开发手测等价物)|🚫 已并入或删除(见《用例分级清单》)
1. 第三人称跟随(1 个用例,含 5 个子状态)
CM-01 第三人称跟随
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| CM-01-01 | 相机跟随角色移动 | 角色在场景中 | 前后左右移动 | 相机平滑跟随,不抖动、不滞后 | 🔴 高 | 🟠 验收(视觉/手感,手工) |
| CM-01-02 | 相机水平旋转 | 角色正常 | 左右移动鼠标 | 相机绕角色水平旋转,角色朝向跟随 | 🔴 高 | 🟠 验收(视觉/手感,手工) |
| CM-01-03 | 相机垂直旋转 | 角色正常 | 上下移动鼠标 | 相机上下倾斜,俯仰视角变化 | 🔴 高 | 🟠 验收(视觉/手感,手工) |
| CM-01-04 | 相机环绕角色 | 角色静止 | 以角色为中心旋转视角 360° | 相机平滑环绕,角色始终在画面中心 | 🟡 中 | 🚫 删除(与 CM-01-02 重复,2026-08-03) |
| CM-01-05 | 角色旋转时相机表现 | 角色正在转向 | 快速转动角色方向 | 相机平滑跟随,不出现瞬间跳转 | 🟡 中 | 🟠 验收(视觉/手感,手工) |
🚫 2026-08-03 删除说明:CM-01-04(相机环绕角色)与 CM-01-02(相机水平旋转)动作重叠——360° 环绕即水平旋转一周,不再单独立项。
2. 碰撞避免(1 个用例,含 4 个子状态)
CM-02 碰撞避免
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| CM-02-01 | 贴墙时相机推近 | 角色背对墙壁 | 向后退到墙边 | 相机逐渐推近角色,不穿模 | 🔴 高 | 🟠 验收(视觉/手感,手工) |
| CM-02-02 | 离开墙壁后恢复 | 相机被墙壁推近后 | 向前离开墙壁 | 相机平滑恢复至正常位置 | 🔴 高 | 🟠 验收(视觉/手感,手工) |
| CM-02-03 | 侧向碰撞 | 角色靠近墙壁的右侧 | 向左移动使相机扫过墙壁 | 相机推近避免穿模,平滑处理 | 🟡 中 | 🟠 验收(视觉/手感,手工) |
| CM-02-04 | 墙角场景 | 角色站在墙角 | 围绕墙角移动 | 相机始终不穿墙 | 🟡 中 | 🟠 验收(视觉/手感,手工) |
3. FOV 与变焦 — 🚫 已删(2026-08-03)
🚫 删除说明:游戏不存在可调 FOV(相机模式固定默认 80°,无瞄准变焦/变焦恢复实现),CM-03-01~03 全部移除。
4. 模式切换与过渡 — 🚫 已删(2026-08-03)
🚫 删除说明:游戏不存在相机模式切换(能力相机/默认相机无接入,仅 Cheat 命令可切),CM-04-01~03 全部移除。
5. 蹲下偏移(1 个用例,含 2 个子状态)
CM-05 蹲下偏移
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| CM-05-01 | 蹲下时相机降低 | 角色正常 | 按蹲下键 | 相机高度平滑降低至蹲姿高度 | 🟡 中 | 🟠 验收(视觉/手感,手工) |
| CM-05-02 | 站起时相机恢复 | 角色蹲下 | 按蹲下键站起 | 相机高度平滑恢复至站姿高度 | 🟡 中 | 🟠 验收(视觉/手感,手工) |
6. 数值测试 🤖 自动化测试(2 个用例,含 9 个子状态)
测试方法:边界值分析。对每个参数定义有效区间 [a, b],按实际风险从 B={a-ε, a, (a+b)/2, b} 中选取测试点,重点覆盖除零/符号反转/数值溢出风险。
NV-01 FOV 参数验证(有效区间 [5.0, 170.0],项目 clamp 值)
| 编号 | 细分状态 | 配置 FOV | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| NV-01-01 | ⚠️ a-ε 低于最小 | 4.9 | 进入游戏 | 不崩溃,FOV clamp 到 5.0 或最小值 | 除零:FOV=0 导致投影矩阵除零崩溃 | 🔴 高 | ⚪ 回归·单元(clamp 校验) |
| NV-01-02 | a 最小值 | 5.0 | 进入游戏 | FOV 显示为最小有效值(5°) | — | 🟡 中 | ⚪ 回归·单元(clamp 校验) |
| NV-01-03 | (a+b)/2 中间值 | 87.5 | 进入游戏 | FOV 显示正常(项目默认 80° 附近) | — | 🔴 高 | ⚪ 回归·单元(数据校验) |
| NV-01-04 | b 最大值 | 170.0 | 进入游戏 | FOV 显示为最大有效值(170°) | — | 🟡 中 | ⚪ 回归·单元(clamp 校验) |
NV-02 俯仰限幅参数验证(有效区间 [-89.0, 89.0],项目定义值)
| 编号 | 细分状态 | 配置俯仰限幅 | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| NV-02-01 | ⚠️ a-ε 低于下限 | -89.1 | 上下移动视角 | 不崩溃,视角被 clamp 到 -89.0 | 越界→视角锁定/抖动 | 🟡 中 | ⚪ 回归·单元(clamp 校验) |
| NV-02-02 | a 下限 | -89.0 | 上下移动视角 | 视角最大俯角为 -89°(项目默认) | — | 🟡 中 | ⚪ 回归·单元(数据校验) |
| NV-02-03 | (a+b)/2 中间值 | 0 | 上下移动视角 | 视角可正常俯仰 | — | 🔴 高 | ⚪ 回归·单元(数据校验) |
| NV-02-04 | b 上限 | 89.0 | 上下移动视角 | 视角最大仰角为 89°(项目默认) | — | 🟡 中 | ⚪ 回归·单元(数据校验) |
| NV-02-05 | ⚠️ b+ε 超出上限 | 89.1 | 上下移动视角 | 不崩溃,视角被 clamp 到 89.0 | 越界→视角翻转 | 🟡 中 | ⚪ 回归·单元(clamp 校验) |
NV-03 混合时间参数验证 — 🚫 已删(2026-08-03)
🚫 删除说明:无相机模式切换即无 BlendTime 过渡,NV-03-01~03 全部移除。
代码实现思路:
1 | 环境: FMapTestSpawner(L_Expanse) → 获取 PlayerCameraManager / UCameraComponent |
HUD — 测试宪章
测试范围
| # | 测试项 | 说明 |
|---|---|---|
| 1 | 准星 🖐️ | 不同武器的准星样式、散布缩放反馈,共 1 个用例(含 6 个子状态) |
| 2 | 命中标记 🖐️ | 命中敌人时的视觉确认标记 + 击杀特殊标记,共 1 个用例(含 5 个子状态) |
| 3 | 伤害数字 🖐️ | 命中时浮出的伤害数值,含暴击区分,共 1 个用例(含 4 个子状态) |
| 4 | HUD 布局 🖐️ | ESC 菜单、手柄断开提示等层级管理,共 1 个用例(含 3 个子状态) |
| 5 | 游戏内信息 🖐️ | 血条、当前武器信息、比分板,共 1 个用例(含 5 个子状态) |
HUD 全部为手工测试,所有元素需要视觉判断。
测试计划
| 测试方式 | 适用用例 |
|---|---|
| 🖐️ 手工测试 | 全部 22 个用例 |
详细分配
| 类别 | 手工测试 | C++ CQTest |
|---|---|---|
| 准星 (HD-01) | 1 个全部(含 6 个子状态) | 无 |
| 命中标记 (HD-02) | 1 个全部(含 5 个子状态) | 无 |
| 伤害数字 (HD-03) | 1 个全部(含 4 个子状态) | 无 |
| HUD 布局 (HD-04) | 1 个全部(含 3 个子状态) | 无 |
| 游戏内信息 (HD-05) | 1 个全部(含 5 个子状态) | 无 |
🎯 用例目的图例:🔵 冒烟(环境/链路基线,失败优先怀疑测试环境)|🟢 回归(防回归保护)|⚪ 回归·单元(数值/配置校验,降级为单元级断言,不走 PIE)|🟠 验收(功能可用性/视觉手感,开发手测等价物)|🚫 已并入或删除(见《用例分级清单》)
1. 准星(1 个用例,含 6 个子状态)
HD-01 准星显示
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| HD-01-01 | 准星显示 | 持有武器 | 进入游戏 | 屏幕中央显示当前武器的准星 | 🔴 高 | 🟠 验收(视觉,手工) |
| HD-01-02 | 不同武器不同准星 | 持有武器 A 和武器 B | 切换武器 | 准星样式随武器切换而改变 | 🟡 中 | 🟠 验收(视觉,手工) |
| HD-01-03 | 散布反馈 — 连续射击 | 持有武器 | 连续射击 | 准星随散布增大而放大 | 🟡 中 | 🟠 验收(视觉,手工) |
| HD-01-04 | 散布反馈 — 停止后恢复 | 准星已放大 | 停止射击等待数秒 | 准星随散布恢复而缩小 | 🟡 中 | 🟠 验收(视觉,手工) |
| HD-01-05 | 移动时准星变化 | 持有武器 | 跑动中观察准星 | 跑动时准星比静止时更大 | 🟡 中 | 🟠 验收(视觉,手工) |
| HD-01-06 | 空手时无准星 | 未持有武器 | 切换到空手 | 屏幕中央无准星显示 | 🟡 中 | 🚫 删除(游戏无空手状态,2026-08-03) |
🚫 2026-08-03 删除说明:游戏无空手状态(EQ-02-03 切空槽为“无反应,保持当前武器”),HD-01-06 前置条件不成立。
2. 命中标记(1 个用例,含 5 个子状态)
HD-02 命中标记
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| HD-02-01 | 命中敌人显示标记 | 持有武器 | 射击命中敌人 | 屏幕中央/命中位置出现命中标记 | 🔴 高 | 🟠 验收(视觉,手工) |
| HD-02-02 | 命中标记消退 | 命中标记已显示 | 等待数秒 | 命中标记逐渐淡出消失 | 🟡 中 | 🟠 验收(视觉,手工) |
| HD-02-03 | 连续命中多个标记 | 持有连发武器 | 连续命中敌人多次 | 每次命中都有对应的命中标记 | 🟡 中 | 🟠 验收(视觉,手工) |
| HD-02-04 | 未命中时无标记 | 持有武器 | 射击未命中目标 | 无命中标记显示 | 🔴 高 | 🟠 验收(视觉,手工) |
| HD-02-05 | 击杀特殊标记 | 持有武器,敌人血量可被一击击杀 | 射击击杀敌人 | 击杀时显示特殊标记,样式与普通命中标记明显区分 | 🔴 高 | 🟠 验收(视觉,手工) |
3. 伤害数字(1 个用例,含 4 个子状态)
HD-03 伤害数字
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| HD-03-01 | 命中显示伤害数字 | 持有武器 | 射击命中敌人 | 命中位置浮出伤害数字 | 🟡 中 | 🟠 验收(视觉,手工) |
| HD-03-02 | 伤害数值正确 | 持有武器 | 射击命中敌人 | 伤害数字显示的值与实际伤害一致 | 🟡 中 | 🟠 验收(视觉,手工) |
| HD-03-03 | 暴击区分 | 武器可造成暴击 | 触发暴击 | 暴击伤害数字样式与普通伤害不同 | 🟡 中 | 🟠 验收(视觉,手工) |
| HD-03-04 | 伤害数字消退 | 伤害数字已显示 | 等待 | 伤害数字逐渐消失 | 🟡 中 | 🟠 验收(视觉,手工) |
4. HUD 布局(1 个用例,含 3 个子状态)
HD-04 HUD 布局
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| HD-04-01 | ESC 菜单 | 游戏中 | 按 ESC 键 | 弹出暂停菜单 | 🔴 高 | 🟠 验收(视觉,手工) |
| HD-04-02 | 手柄断开提示 | 使用手柄 | 断开手柄连接 | 屏幕显示控制器断开连接提示 | 🟡 中 | 🟠 验收(视觉,手工) |
| HD-04-03 | HUD 元素布局 | 进入游戏 | 检查各 UI 元素位置 | 准星、血量、弹药等元素在正确位置显示 | 🟡 中 | 🟠 验收(视觉,手工) |
5. 游戏内信息(1 个用例,含 5 个子状态)
HD-05 游戏内信息
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| HD-05-01 | 血条显示 | 角色正常 | 进入游戏 | 屏幕上显示血量条/数值 | 🔴 高 | 🟠 验收(视觉,手工) |
| HD-05-02 | 受伤后血条变化 | 角色有血量 | 受到伤害 | 血条随着伤害实时减少 | 🔴 高 | 🟠 验收(视觉,手工) |
| HD-05-03 | 治疗回血 | 角色血量不满 | 接受治疗 | 血条随着治疗增加 | 🟡 中 | 🟠 验收(视觉,手工) |
| HD-05-04 | 当前武器信息 | 持有武器 | 检查屏幕 | 显示当前武器的名称/图标/弹药量 | 🔴 高 | 🟠 验收(视觉,手工) |
| HD-05-05 | 弹药量更新 | 持有武器 | 射击消耗弹药 | 弹药数字随射击减少 | 🔴 高 | 🟠 验收(视觉,手工) |
比分板测试已移至队伍系统
前端页面 — 测试宪章
测试范围
| # | 测试项 | 说明 |
|---|---|---|
| 1 | 加载动画 🚫 | 已删(2026-08-03:与 FE-05 加载画面重复) |
| 2 | 主菜单 | 导航、游戏模式选择、进入游戏,共 1 个用例(含 5 个子状态) |
| 3 | 设置界面 | 画面/音频/控制等设置,共 1 个用例(含 4 个子状态) |
| 4 | 确认弹窗 | 退出确认、操作确认等模态弹窗,共 1 个用例(含 3 个子状态) |
| 5 | 加载画面 | 菜单间切换/进入游戏时的加载界面,共 1 个用例(含 3 个子状态) |
| 6 | ESC 暂停菜单 | 游戏中的暂停菜单、回到大厅、退出游戏,共 1 个用例(含 3 个子状态) |
前端页面全部可自动化测试(UI 交互流程可通过脚本验证)。
测试计划
| 测试方式 | 适用用例 |
|---|---|
| 🤖 Spec + Gauntlet | 全部 5 个用例(含 18 个子状态) |
详细分配
| 类别 | 手工测试 | Spec + Gauntlet |
|---|---|---|
| 全部 (FE-02~06) | 无 | 5 个全部(含 18 个子状态) |
🎯 用例目的图例:🔵 冒烟(环境/链路基线,失败优先怀疑测试环境)|🟢 回归(防回归保护)|⚪ 回归·单元(数值/配置校验,降级为单元级断言,不走 PIE)|🟠 验收(功能可用性/视觉手感,开发手测等价物)|🚫 已并入或删除(见《用例分级清单》)
1. 加载动画 — 🚫 已删(2026-08-03)
🚫 删除说明:FE-01(加载动画)与 FE-05(加载画面)覆盖同一加载链路,已并入 FE-05,不再单独立项。
2. 主菜单(1 个用例,含 5 个子状态)
FE-02 主菜单
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| FE-02-01 | 主菜单显示 | 进入主菜单 | 等待菜单加载 | 显示游戏模式选择、设置、退出等选项 | 🔴 高 | 🔵 冒烟(启动链路) |
| FE-02-02 | 选择游戏模式 | 主菜单中 | 点击一个游戏模式 | 进入该模式加载流程 | 🔴 高 | 🔵 冒烟 + 🟢 回归(进入对局路径) |
| FE-02-03 | 导航到设置 | 主菜单中 | 点击设置按钮 | 切换到设置界面 | 🔴 高 | 🟢 回归(导航流程) |
| FE-02-04 | 返回主菜单 | 在子页面中 | 按返回键 | 回到主菜单 | 🟡 中 | 🟢 回归(导航流程) |
| FE-02-05 | 主菜单背景 | 主菜单中 | 观察背景 | 背景场景正常显示(大厅/角色展示) | 🟡 中 | 🟢 回归(资源存在性) |
代码实现思路:
1 | FE-02(主菜单): |
3. 设置界面(1 个用例,含 4 个子状态)
FE-03 设置界面
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| FE-03-01 | 设置页面布局 | 进入设置 | 浏览各设置标签页 | 画面/音频/控制等标签页正常切换 | 🟡 中 | 🟢 回归(设置流程) |
| FE-03-02 | 修改设置 | 设置界面中 | 修改一个设置项 | 设置值即时更新或显示已更改标记 | 🟡 中 | 🟢 回归(设置流程) |
| FE-03-03 | 保存设置 | 已修改设置 | 应用/保存 | 设置保存,下次启动时仍然有效 | 🔴 高 | 🟢 回归(设置持久化) |
| FE-03-04 | 取消设置 | 已修改设置 | 不保存并退出 | 设置恢复到修改前的状态 | 🟡 中 | 🟢 回归(设置流程) |
代码实现思路:
1 | FE-03(设置界面): |
4. 确认弹窗(1 个用例,含 3 个子状态)
FE-04 确认弹窗
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| FE-04-01 | 退出游戏确认 | 主菜单中 | 选择退出游戏 | 弹出确认弹窗,有确认/取消选项 | 🔴 高 | 🟢 回归(弹窗流程) |
| FE-04-02 | 确认弹窗 — 确认 | 确认弹窗已显示 | 点击确认 | 执行对应操作(退出游戏) | 🔴 高 | 🟢 回归(弹窗流程) |
| FE-04-03 | 确认弹窗 — 取消 | 确认弹窗已显示 | 点击取消 / 按 ESC | 弹窗关闭,不执行操作 | 🟡 中 | 🟢 回归(弹窗流程) |
代码实现思路:
1 | FE-04(确认弹窗): |
5. 加载画面(1 个用例,含 3 个子状态)
FE-05 加载画面
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| FE-05-01 | 进入游戏时加载画面 | 选择游戏模式后 | 等待加载 | 显示加载画面(有提示/进度) | 🔴 高 | 🔵 冒烟 + 🟢 回归(加载链路) |
| FE-05-02 | 加载完成后进入游戏 | 加载画面中 | 加载完成 | 加载画面消失,进入游戏 | 🔴 高 | 🔵 冒烟 + 🟢 回归(加载链路) |
| FE-05-03 | 加载画面期间输入 | 加载画面中 | 按任意键 | 加载不受影响,继续执行 | 🟡 中 | 🟢 回归(加载中输入) |
代码实现思路:
1 | FE-05(加载画面): |
6. ESC 暂停菜单(1 个用例,含 3 个子状态)
FE-06 暂停菜单
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| FE-06-01 | 暂停菜单 | 游戏中 | 按 ESC | 弹出暂停菜单 | 🔴 高 | 🟢 回归(暂停流程) |
| FE-06-02 | 继续游戏 | 暂停菜单中 | 点击继续 | 暂停菜单关闭,返回游戏 | 🔴 高 | 🟢 回归(暂停流程) |
| FE-06-03 | 从暂停菜单回到大厅 | 暂停菜单中 | 选择回到大厅 | 退出当前对局,回到主菜单 | 🟡 中 | 🟢 回归(退出流程) |
代码实现思路:
1 | FE-06(ESC暂停): |
音频系统 — 测试宪章
测试范围
| # | 测试项 | 说明 |
|---|---|---|
| 1 | 音量设置 🖐️ | 总体/音乐/SFX/对白/语音 音量滑块调节,共 1 个用例(含 5 个子状态) |
| 2 | 音频输出 🖐️ | 切换音频输出设备(耳机/扬声器),共 1 个用例(含 2 个子状态) |
| 3 | HDR 音频 🖐️ | 高动态范围音频开关切换,共 1 个用例(含 2 个子状态) |
| 4 | 加载画面音频 🖐️ | 加载画面期间的音频表现,共 1 个用例(含 2 个子状态) |
| 5 | 数值测试 🤖 | 音量边界值 + 通道独立调节,共 2 个用例(含 5 个子状态) |
测试计划
| 测试方式 | 适用用例 |
|---|---|
| 🖐️ 手工测试 | AU-01 ~ AU-04 |
| 🤖 C++ CQTest | NV-01 ~ NV-02 |
详细分配
| 类别 | 手工测试 | C++ CQTest |
|---|---|---|
| 音量设置 (AU-01) | 1 个全部(含 5 个子状态) | 无 |
| 音频输出 (AU-02) | 1 个全部(含 2 个子状态) | 无 |
| HDR 音频 (AU-03) | 1 个全部(含 2 个子状态) | 无 |
| 加载画面音频 (AU-04) | 1 个全部(含 2 个子状态) | 无 |
| 数值测试 (NV) | 无 | 2 个全部(含 5 个子状态) |
🎯 用例目的图例:🔵 冒烟(环境/链路基线,失败优先怀疑测试环境)|🟢 回归(防回归保护)|⚪ 回归·单元(数值/配置校验,降级为单元级断言,不走 PIE)|🟠 验收(功能可用性/视觉手感,开发手测等价物)|🚫 已并入或删除(见《用例分级清单》)
1. 音量设置(1 个用例,含 5 个子状态)
AU-01 音量设置
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| AU-01-01 | 总体音量调节 | 游戏中 | 在设置中调整总体音量滑块 | 游戏所有音量随之变化 | 🔴 高 | 🟠 验收(听感/设置生效,手工) |
| AU-01-02 | 音乐音量调节 | 游戏中有背景音乐 | 调整音乐音量 | 背景音乐音量变化,SFX 不受影响 | 🟡 中 | 🟠 验收(听感/设置生效,手工) |
| AU-01-03 | SFX 音量调节 | 游戏中有音效 | 调整 SFX 音量 | 射击/脚步等音效变化,音乐不受影响 | 🔴 高 | 🟠 验收(听感/设置生效,手工) |
| AU-01-04 | 对白音量调节 | 游戏中有对白 | 调整对白音量 | 对白音量变化 | 🟢 低 | 🟠 验收(听感/设置生效,手工) |
| AU-01-05 | 语音聊天音量 | 多人游戏中 | 调整语音音量 | 其他玩家的语音音量变化 | 🟢 低 | 🟠 验收(听感/设置生效,手工) |
2. 音频输出(1 个用例,含 2 个子状态)
AU-02 音频输出
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| AU-02-01 | 切换音频输出设备 | 游戏中 | 在设置中切换音频输出设备(耳机→扬声器) | 音频从新设备输出 | 🟡 中 | 🟠 验收(听感,手工) |
| AU-02-02 | 设备切换失败处理 | 设置中 | 切换到一个不存在的设备 | 不崩溃,保持当前设备或显示错误提示 | 🟡 中 | 🟠 验收(容错,手工) |
3. HDR 音频(1 个用例,含 2 个子状态)
AU-03 HDR 音频
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| AU-03-01 | 开启 HDR 音频 | 默认 LDR 模式 | 在设置中开启 HDR 音频 | 音频动态范围增强,音质变化可感知 | 🟢 低 | 🟠 验收(听感,手工) |
| AU-03-02 | 关闭 HDR 音频 | HDR 模式 | 关闭 HDR 音频 | 恢复到 LDR 音频表现 | 🟢 低 | 🟠 验收(听感,手工) |
4. 加载画面音频(1 个用例,含 2 个子状态)
AU-04 加载画面音频
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| AU-04-01 | 加载画面期间音频淡出 | 进入加载画面 | 观察加载过程中音频 | 游戏音频逐渐淡出或切换到加载画面背景音 | 🟡 中 | 🟠 验收(听感,手工) |
| AU-04-02 | 加载完成后音频恢复 | 加载完成 | 加载完成进入游戏 | 音频恢复正常 | 🟡 中 | 🟠 验收(听感,手工) |
5. 数值测试 🤖 自动化测试(2 个用例,含 5 个子状态)
NV-01 音量参数验证(有效区间 [0, 1.0])
| 编号 | 细分状态 | 配置音量 | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| NV-01-01 | ⚠️ a-ε 低于最小 | -0.01 | 播放任意音效 | 不崩溃,音量为 0 | 负值导致音频计算异常/除零 | 🟡 中 | ⚪ 回归·单元(clamp 校验) |
| NV-01-02 | a 最小值 | 0 | 播放任意音效 | 所有音频静音 | — | 🟡 中 | ⚪ 回归·单元(边界校验) |
| NV-01-03 | (a+b)/2 中间值 | 0.5 | 播放任意音效 | 音频正常播放,音量为 50% | — | 🔴 高 | ⚪ 回归·单元(数据校验) |
| NV-01-04 | b 最大值 | 1.0 | 播放任意音效 | 音频正常播放,不破音 | 浮点溢出→音频裁剪失真 | 🟡 中 | ⚪ 回归·单元(边界校验) |
NV-02 各通道音量独立调节
| 编号 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|
| NV-02 | 分别调整各通道 | 逐一验证 | 各通道音量独立生效,互不影响 | 🟡 中 | ⚪ 回归·单元(通道独立) |
代码实现思路:
1 | 环境: 单元测试,无需PIE → ULyraSettingsLocal(UPROPERTY Config) |
队伍系统 — 测试宪章
测试范围
| # | 测试项 | 说明 |
|---|---|---|
| 1 | 团队归属 | 分配到队伍 + 切换 + 平衡,共 1 个用例(含 4 个子状态) |
| 2 | 友军识别 | 队友/敌人标记 + 伤害判定,共 1 个用例(含 3 个子状态) |
| 3 | 团队显示 | 队伍颜色 + 名称 + 更新,共 1 个用例(含 3 个子状态) |
| 4 | 团队标签 | 队伍得分/击杀统计,共 1 个用例 |
| 5 | 战绩统计 | 比分板 + 个人战绩 + 实时更新,共 1 个用例(含 3 个子状态) |
| 6 | 数值测试 🚫 | 已删(2026-08-03:游戏无队伍数量参数) |
测试计划
| 测试方式 | 适用用例 |
|---|---|
| 🖐️ 手工测试 | TM-01 ~ TM-05 |
详细分配
| 类别 | 手工测试 | C++ CQTest |
|---|---|---|
| 团队归属 (TM-01) | 1 个全部(含 4 个子状态) | 无 |
| 友军识别 (TM-02) | 1 个全部(含 3 个子状态) | 无 |
| 团队显示 (TM-03) | 1 个全部(含 3 个子状态) | 无 |
| 团队标签 (TM-04) | 1 个全部 | 无 |
| 战绩统计 (TM-05) | 1 个全部(含 3 个子状态) | 无 |
| 数值测试 (NV-01) | 🚫 已删(2026-08-03) | — |
🎯 用例目的图例:🔵 冒烟(环境/链路基线,失败优先怀疑测试环境)|🟢 回归(防回归保护)|⚪ 回归·单元(数值/配置校验,降级为单元级断言,不走 PIE)|🟠 验收(功能可用性/视觉手感,开发手测等价物)|🚫 已并入或删除(见《用例分级清单》)
1. 团队归属(1 个用例,含 4 个子状态)
TM-01 团队归属
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| TM-01-01 | 进入游戏时分配到队伍 | 玩家加入游戏 | 进入对局 | 玩家被分配到某个队伍(红队或蓝队) | 🔴 高 | 🟠 验收(多人场景,手工) |
| TM-01-02 | 同一队伍有多个玩家 | 多个玩家加入 | 全部加入同一队伍 | 同队玩家共享相同的队伍标识 | 🔴 高 | 🟠 验收(多人场景,手工) |
| TM-01-03 | 切换队伍 | 游戏中 | 切换到另一队伍 | 玩家队伍标识更新,位置/出生点可能改变 | 🟡 中 | 🟠 验收(多人场景,手工) |
| TM-01-04 | 队伍人数平衡 | 两队人数不均 | 新玩家加入 | 新玩家分配到人数较少的一方 | 🟡 中 | 🟠 验收(平衡逻辑,手工) |
2. 友军识别(1 个用例,含 3 个子状态)
TM-02 友军识别
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| TM-02-01 | 友军标记 | 队友在视野内 | 观察队友 | 队友头上有友军标记(颜色/图标区分) | 🔴 高 | 🟠 验收(视觉,手工) |
| TM-02-02 | 敌军标记 | 敌人在视野内 | 观察敌人 | 敌人不同颜色 | 🔴 高 | 🟠 验收(视觉,手工) |
| TM-02-03 | ⚠️ 友军伤害 | 对友军射击 | 射击队友 | 友军不受伤害子弹消失 | 🔴 高 | 🟠 验收(判定,手工;可考虑自动化) |
⚠️ TM-02-03 风险:友军伤害开关异常可能导致友军误伤或自伤
3. 团队显示(1 个用例,含 3 个子状态)
TM-03 团队显示
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| TM-03-01 | 队伍颜色 | 游戏开始 | 观察各队伍 | 不同队伍有不同颜色标识(红/蓝等) | 🔴 高 | 🟠 验收(视觉,手工) |
| TM-03-02 | 队伍名称 | 游戏中 | 查看比分板或队友头顶 | 显示队伍名称 | 🟡 中 | 🟠 验收(视觉,手工) |
| TM-03-03 | 换队后颜色更新 | 切换队伍后 | 切换到另一队 | 玩家颜色/标记更新为新队伍 | 🟡 中 | 🚫 删除(游戏无换队功能,2026-08-03) |
🚫 2026-08-03 删除说明:TM-03-03(换队后颜色更新)依赖换队功能,TM-01 已删,游戏无换队入口,移除。
4. 团队标签(1 个用例)
TM-04 团队标签
| 编号 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|
| TM-04 | 游戏中 | 查看比分板 | 显示各队伍的总击杀/得分 | 🟡 中 | 🟠 验收(视觉,手工) |
5. 战绩统计(1 个用例,含 3 个子状态)
TM-05 战绩统计
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| TM-05-01 | 比分板显示 | 游戏中 | 按比分板键 | 显示双方队伍得分/击杀/死亡数据 | 🟡 中 | 🟠 验收(视觉,手工) |
| TM-05-02 | 个人战绩 | 游戏中 | 查看比分板 | 显示每个玩家的击杀/死亡/助攻 | 🟡 中 | 🟠 验收(视觉,手工) |
| TM-05-03 | 战绩实时更新 | 游戏中 | 击杀敌人后查看比分板 | 击杀数/得分实时更新 | 🟡 中 | 🟠 验收(视觉,手工) |
6. 数值测试 — 🚫 已删(2026-08-03)
🚫 删除说明:游戏不存在队伍数量配置参数,NV-01-01~04 全部移除。
初始化链路系统 — 测试宪章
测试范围
| # | 测试项 | 说明 |
|---|---|---|
| 1 | 游戏启动 | 进程/Experience/网络同步,共 1 个用例(含 3 个子状态) |
| 2 | 资源生成 | Pawn/AbilitySet/武器/属性/拾取/特效,共 1 个用例(含 6 个子状态) |
| 3 | 重生 | 重生链路 + 状态重置,共 1 个用例(含 2 个子状态) |
| 4 | 输入绑定 | PlayerController 绑定 + 输入初始化,共 1 个用例(含 2 个子状态) |
| 5 | 断线重连 | 重连 + 状态恢复 + 资源防重复,共 1 个用例(含 3 个子状态) |
| 6 | 数值边界 🤖 | 重生延迟边界值,共 1 个用例(含 3 个子状态) |
全部自动化测试。目标:验证初始化链路完整跑通,所有必要资源存在且正确加载。
测试计划
| 测试方式 | 适用用例 |
|---|---|
| 🤖 C++ CQTest + Gauntlet | 全部 6 个用例(含 19 个子状态) |
详细分配
| 类别 | 手工测试 | C++ CQTest |
|---|---|---|
| 全部 (IN-01~05, NV-01) | 无 | 6 个全部(含 19 个子状态) |
🎯 用例目的图例:🔵 冒烟(环境/链路基线,失败优先怀疑测试环境)|🟢 回归(防回归保护)|⚪ 回归·单元(数值/配置校验,降级为单元级断言,不走 PIE)|🟠 验收(功能可用性/视觉手感,开发手测等价物)|🚫 已并入或删除(见《用例分级清单》)
1. 游戏启动(1 个用例,含 3 个子状态)
IN-01 游戏启动
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| IN-01-01 | 游戏进程启动 | 启动游戏 | 拉起游戏进程 | 进程正常启动,无崩溃,日志无错误 | 🔴 高 | 🔵 冒烟(环境基线) |
| IN-01-02 | Experience 加载 | 选择游戏模式 | 触发 Experience 加载 | Experience 资源加载完成,GameMode 初始化成功 | 🔴 高 | 🔵 冒烟(链路基线) |
| IN-01-03 | 网络同步就绪 | 客户端连接服务器 | 等待同步 | 客户端与服务器同步完成,GameState 正确复制 | 🔴 高 | 🔵 冒烟 + 🟢 回归(网络同步) |
代码实现思路:
1 | 环境: Gauntlet BootTest → 进程+日志+同步 |
2. 资源生成(1 个用例,含 6 个子状态)
IN-02 资源生成
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| IN-02-01 | Pawn 资源加载 | 玩家加入 | 检查 Pawn 生成 | Pawn Class 存在,生成无报错 | 🔴 高 | 🔵 冒烟(链路基线) |
| IN-02-02 | AbilitySet 资源 | Pawn 生成后 | 验证 AbilitySet | 所有 GrantedAbilities 正确注册到 ASC | 🔴 高 | 🔵 冒烟 + 🟢 回归(能力注册) |
| IN-02-03 | 默认武器资源 | Pawn 生成后 | 验证武器 | 默认武器资源(Mesh/Texture)存在且加载 | 🔴 高 | 🔵 冒烟 + 🟢 回归(资源存在性) |
| IN-02-04 | 角色属性初始化 | Pawn 生成后 | 验证属性 | Health/MaxHealth 初始值为满值,无 NaN | 🔴 高 | 🟢 回归(属性初始化) |
| IN-02-05 | 拾取武器资源 | 拾取武器后 | 验证生成 | 武器 Actor 生成,武器模型资源加载完成 | 🟡 中 | 🟢 回归(资源生成) |
| IN-02-06 | 特效资源加载 | 触发特效时 | 验证特效 | 粒子/音效资源已加载,播放无资源缺失 | 🟡 中 | 🟢 回归(资源存在性) |
代码实现思路:
1 | 环境: FMapTestSpawner(L_Expanse) → FindFirstPlayerPawn |
3. 重生(1 个用例,含 2 个子状态)
IN-03 重生
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| IN-03-01 | 重生链路 | 角色死亡 | 等待重生 | 重生后 Pawn 重新生成,链路完整跑通 | 🔴 高 | 🟢 回归(与 DT-06 重叠,建议并入) |
| IN-03-02 | 重生后状态重置 | 角色死亡 | 重生后检查 | 重生后 AbilitySet 重新授予,装备重置 | 🔴 高 | 🟢 回归(与 DT-06 重叠,建议并入) |
代码实现思路:
1 | 环境: FMapTestSpawner(L_Expanse) → 致死伤害+重生 |
4. 输入绑定(1 个用例,含 2 个子状态)
IN-04 输入绑定
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| IN-04-01 | PlayerController 绑定 | Pawn 生成后 | 验证绑定 | PlayerController 正确 Possess Pawn | 🔴 高 | 🔵 冒烟(输入链路基线) |
| IN-04-02 | 输入组件初始化 | Pawn 生成后 | 验证输入 | 输入组件(EnhancedInput)已初始化 | 🔴 高 | 🔵 冒烟(输入链路基线) |
代码实现思路:
1 | 环境: FMapTestSpawner(L_Expanse) → PC+Pawn |
5. 断线重连(1 个用例,含 3 个子状态)
IN-05 断线重连
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| IN-05-01 | 断线后重连 | 游戏中断开网络 | 重新连接 | 客户端重连成功,重新进入游戏 | 🟡 中 | 🟢 回归(网络恢复) |
| IN-05-02 | 重连后状态恢复 | 重连成功 | 检查状态 | 角色 AbilitySet 重新授予,状态正确 | 🟡 中 | 🟢 回归(状态恢复) |
| IN-05-03 | 资源重复加载 | 多次重连 | 检查资源 | 资源不重复加载,内存无泄漏 | 🟡 中 | 🟢 回归(资源防重复) |
代码实现思路:
1 | 环境: Gauntlet(客户端+服务端) |
6. 数值边界 🤖 自动化测试(1 个用例,含 3 个子状态)
NV-01 重生延迟参数验证(有效区间 [0, 60])
| 编号 | 细分状态 | 配置重生延迟 | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| NV-01-01 | a 最小值 | 0 | 角色死亡 | 角色立即重生,链路无异常 | 除零:延迟为0导致Timer/FPS除零 | 🔴 高 | 🟢 回归(数值边界:重生延迟 0) |
| NV-01-02 | (a+b)/2 中间值 | 30 | 角色死亡 | 等待约 30 秒后重生 | — | 🔴 高 | 🟢 回归(数值边界) |
| NV-01-03 | b 最大值 | 60 | 角色死亡 | 等待约 60 秒后重生 | — | 🔴 高 | 🟢 回归(数值边界) |
⚠️ NV-01-01 风险:重生延迟为 0 可能导致 Timer 异常或帧循环除零
代码实现思路:
1 | 环境: FMapTestSpawner(L_Expanse) → GameMode重生配置 |
状态系统 — 测试宪章
🚫 已砍(2026-08-02 决策):状态系统不再单独测试。
血量/死亡相关验证并入事件测试(DT-01 血量归零触发死亡);本文档仅作历史参考,不实现 ST/NV CQTest。
测试范围
| # | 测试项 | 说明 |
|---|---|---|
| 1 | 生命属性 | 血量/最大血量、受伤/治疗,共 1 个用例(含 4 个子状态) |
| 2 | 死亡状态机 🖐️ | NotDead → DeathStarted → DeathFinished 流转,共 1 个用例(含 3 个子状态) |
| 3 | 数值测试 🤖 | 初始血量 + 最大血量边界,共 2 个用例(含 7 个子状态) |
测试计划
| 测试方式 | 适用用例 |
|---|---|
| 🖐️ 手工测试 | ST-02(死亡状态机视觉验证) |
| 🤖 C++ CQTest | ST-01, NV-01 ~ NV-02 |
详细分配
| 类别 | 手工测试 | C++ CQTest |
|---|---|---|
| 生命属性 (ST-01) | 无 | 1 个全部(含 4 个子状态) |
| 死亡状态机 (ST-02) | 1 个全部(含 3 个子状态) | 无 |
| 数值测试 (NV) | 无 | 2 个全部(含 7 个子状态) |
1. 生命属性(1 个用例,含 4 个子状态)
ST-01 生命属性
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 |
|---|---|---|---|---|---|
| ST-01-01 | 初始血量为满值 | 角色生成 | 读取血量 | 当前血量 = 最大血量 | 🔴 高 |
| ST-01-02 | 受伤后血量减少 | 角色有血量 | 受到伤害 X | 血量减少 X,不低于 0 | 🔴 高 |
| ST-01-03 | 治疗后血量增加 | 角色血量不满 | 接受治疗 Y | 血量增加 Y,不超过最大值 | 🔴 高 |
| ST-01-04 | 治疗不超上限 | 角色接近满血 | 过量治疗 | 血量不超过最大血量 | 🟡 中 |
2. 死亡状态机 🖐️(1 个用例,含 3 个子状态)
ST-02 死亡状态机
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 |
|---|---|---|---|---|---|
| ST-02-01 | 血量归零触发死亡 | 角色有血量 | 造成致命伤害 | 血量归零,角色进入死亡状态 | 🔴 高 |
| ST-02-02 | 死亡后不可操作 | 角色死亡 | 尝试移动/射击 | 角色不能移动,不能操作 | 🔴 高 |
| ST-02-03 | 死亡视觉反馈 | 角色死亡 | 观察 | 死亡动画/特效播放 | 🟡 中 |
3. 数值测试 🤖 自动化测试(2 个用例,含 7 个子状态)
NV-01 初始血量参数验证(有效区间 [0, MaxHP])
| 编号 | 细分状态 | 配置初始血量 | 测试步骤 | 预期结果 | 风险关注 | 优先级 |
|---|---|---|---|---|---|---|
| NV-01-01 | ⚠️ a-ε 低于最小 | -1 | 角色生成 | 不崩溃,血量 clamp 到 0 或取绝对值 | 负血量→HUD 显示异常/治疗循环死锁 | 🔴 高 |
| NV-01-02 | a 最小值 | 0 | 角色生成 | 角色生成时血量为 0,直接死亡或特殊状态 | 除零:血量为0导致百分比计算除零 | 🔴 高 |
| NV-01-03 | (a+b)/2 中间值 | 50 | 角色生成 | 角色以 50 点血量生成 | — | 🔴 高 |
| NV-01-04 | b 最大值 | 100 | 角色生成 | 角色以最大血量 100 生成 | — | 🔴 高 |
NV-02 最大血量参数验证(有效区间 [1, 10000])
| 编号 | 细分状态 | 配置最大血量 | 测试步骤 | 预期结果 | 风险关注 | 优先级 |
|---|---|---|---|---|---|---|
| NV-02-01 | a 最小值 | 1 | 角色生成 | 角色以 1 点最大血量生成,血量 = 1 | — | 🟡 中 |
| NV-02-02 | (a+b)/2 中间值 | 5000 | 角色生成 | 角色以 5000 点最大血量生成 | — | 🔴 高 |
| NV-02-03 | b 最大值 | 10000 | 角色生成 | 角色以 10000 点最大血量生成 | 数值溢出→HUD 显示/伤害计算异常 | 🟡 中 |
⚠️ NV-01-01 风险:负血量可能导致 HUD 显示异常、治疗逻辑死循环
⚠️ NV-01-02 风险:血量为 0 可能导致百分比计算除零崩溃
⚠️ NV-02-03 风险:最大血量过高可能导致 HUD 显示或伤害计算数值溢出
代码实现思路:
1 | 环境: FMapTestSpawner(L_Expanse) → ULyraHealthComponent |
事件测试 — 测试宪章
测试范围
| # | 测试项 | 说明 |
|---|---|---|
| 1 | 死亡 🤖/🖐️ | 状态机冻结/摄像机/状态标签/网络同步/边界,共 5 个用例(含 16 个子状态;DT-04 网络暂缓) |
| 2 | 重生 🤖 | 重生后完整性 + 边界情况,共 2 个用例(含 7 个子状态) |
| 3 | 散布系统 🤖 | heat 累积冷却/散布映射/修正乘数/首发精度/曲线边界,共 6 个用例(含 20 个子状态,后续排期) |
📦 2026-08-02 交付范围:DT-01/02/03/05/06/07(DT-04 网络暂缓,SP 后续排期)。术语统一:死亡 / 淘汰 / 重生(Lyra 无”复活”,原”复活”表述全部改称”重生”)。
测试计划
| 测试方式 | 适用用例 | 说明 |
|---|---|---|
| 🖐️ 手工测试 | DT-02-02(重生后摄像机恢复)、DT-05-04(Dash 中死亡) | 视觉/手感判断 |
| 🤖 C++ CQTest | DT-01(含 DT-02-01 相机切换)、DT-03、DT-05-01~03、DT-06、DT-07-01 | 单机死亡/重生链路(2026-08 回归 CQTest,弃用蓝图功能测试) |
| 🤖 C++ CQTest(后续) | DT-04(网络同步)、SP-01 ~ SP-06 | PIE Network + CQTest 实现 |
详细分配
| 类别 | 手工测试 | 蓝图功能测试 | C++ CQTest |
|---|---|---|---|
| 死亡 (DT) | DT-02-02、DT-05-04 | 无(已弃用) | DT-01(含 DT-02-01 相机切换)、DT-03、DT-05-01~03、DT-06、DT-07-01 |
| 散布 (SP) | 无 | 无 | 6 个全部(后续排期) |
🎯 用例目的图例:🔵 冒烟(环境/链路基线,失败优先怀疑测试环境)|🟢 回归(防回归保护)|⚪ 回归·单元(数值/配置校验,降级为单元级断言,不走 PIE)|🟠 验收(功能可用性/视觉手感,开发手测等价物)|🚫 已并入或删除(见《用例分级清单》)
1. 死亡
设计依据
Lyra 死亡状态机:NotDead → DeathStarted → DeathFinished → Actor销毁
1 | HandleOutOfHealth() [Server] |
死亡冻结范围:基础移动(WASD)、高阶移动(跳跃/蹲下/Dash)、碰撞体、交互操作、视角(look 输入)。
死亡后切换 CM_ThirdPerson_Death,视角冻结(2026-08-02 实测修正:原宪章”视角不冻结”有误)。
1.1 死亡状态机与移动冻结 (1 个用例,含 5 个子状态)
DT-01 死亡状态机与移动冻结
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| DT-01-01 | ⚠️ 死亡后基础移动冻结 | 角色死亡 | 死亡后按方向键 | 角色不能移动,不能转向 | 🔴 高 | 🟢 回归(吸收 FR-01) |
| DT-01-02 | ⚠️ 死亡后高阶移动冻结 | 角色死亡 | 死亡后依次尝试跳跃/蹲下/Dash | 角色不能跳跃、不能蹲下、不能 Dash | 🔴 高 | 🟢 回归 |
| DT-01-03 | ⚠️ 死亡后碰撞体冻结 | 角色死亡 | 死亡后其他角色/子弹与尸体碰撞 | 胶囊碰撞体为 NoCollision,无碰撞 | 🔴 高 | 🟢 回归(吸收 DT-05-02) |
| DT-01-04 | ⚠️ 死亡后禁用所有交互 | 角色死亡 | 死亡后按开火/换弹/互动键 | 所有操作无效(Ability System Status.Death 阻断) | 🔴 高 | 🟢 回归(吸收 WF-01-04) |
| DT-01-05 | ⚠️ 死亡后视角冻结 + 相机切换 | 角色死亡 | 死亡后移动鼠标/右摇杆 | 视角冻结:look 输入不影响相机(死亡摄像机 CM_ThirdPerson_Death) | 🟡 中 | 🟢 回归(视角冻结 + 相机模式切换校验) |
⚠️ DT-01-01 / DT-01-04 风险:死亡后如果输入/交互未被完全禁用,玩家可能”死后开枪”或操作幽灵角色
⚠️ DT-01-03 风险:碰撞体必须设为 NoCollision,避免尸体阻挡其他玩家/子弹
⚠️ DT-01-05 风险:死亡摄像机(CM_ThirdPerson_Death)为固定视角,若 look 输入仍能转动相机说明冻结失效
1.2 死亡摄像机 (1 个用例,含 1 个子状态;DT-02-01 已并入 DT-01-05)
🤖 2026-08-03 修正:相机模式切换(DT-02-01)视觉上无法判断是否换了摄像机,校验转 C++(死亡后相机模式栈顶层 = CM_ThirdPerson_Death,并入 DT-01-05);DT-02-02(重生后恢复)保留手工,位置/旋转/FOV/装备复位可观察。
DT-02 死亡摄像机
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| DT-02-01 | 死亡后摄像机切换 | 角色正常 | 击杀角色触发死亡 | 摄像机切换至 CM_ThirdPerson_Death | 🔴 高 | 🚫 并入 DT-01-05(相机切换需校验相机模式栈,手工无法判断,2026-08-03) |
| DT-02-02 | 重生后摄像机恢复 | 角色死亡后重生 | 重生后观察摄像机 | 摄像机切回默认模式,位置/旋转/FOV/装备 全部复位 | 🔴 高 | 🟠 验收(视觉,手工) |
1.3 死亡状态标签 (1 个用例,含 3 个子状态)
DT-03 死亡状态标签
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| DT-03-01 | 死亡后 Status.Death.Dying 标签 | 角色受到致死伤害 | StartDeath 后检查 AbilityTag | 角色挂载 Status.Death.Dying 标签 | 🔴 高 | 🟢 回归(GAS 标签) |
| DT-03-02 | 死亡后 Status.Death.Dead 标签 | 角色死亡 | FinishDeath 后检查 AbilityTag | 挂载 Status.Death.Dead;Dying 与 Dead 并存至 Pawn 销毁(2026-08-02 按代码修正:原”Dying 清除”有误) | 🟡 中 | 🟢 回归(GAS 标签) |
| DT-03-03 | 死亡后 SurvivesDeath 技能保留 | 角色拥有 SurvivesDeath 技能 | 死亡后检查技能列表 | 标记 SurvivesDeath 的技能未被取消,其余被清除 | 🟡 中 | 🟢 回归(GAS 标签) |
1.4 网络同步 (1 个用例,含 2 个子状态) — 暂缓
DT-04 死亡网络同步
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| DT-04-01 | ⚠️ 服务端死亡→客户端死亡状态复制 | PIE 多开 | 服务端击杀角色,观察客户端 | 客户端在合理延迟内复制 DeathState,死亡表现与服务端一致 | 🔴 高 | 🟢 回归(网络复制,后续排期) |
| DT-04-02 | ⚠️ 客户端预测死亡 vs 服务器回滚冲突 | PIE 多开 | 客户端本地预测死亡(如高延迟下),服务端判定未死 | 客户端回滚到非死亡状态,不遗留死亡标签/禁用状态 | 🔴 高 | 🟢 回归(预测回滚,后续排期) |
⚠️ DT-04-01 风险:高延迟下 DeathState 复制延迟可能导致客户端角色已死亡但仍在行走
⚠️ DT-04-02 风险:客户端预测死亡后回滚,如果移动冻结/碰撞禁用未正确恢复,角色变成幽灵
代码实现思路:
2026-08-02 决策:改用轻量测试地图
L_ShooterTest_DeviceProperties(/ShooterTests/Maps),不用 L_Expanse(加载太重)。实现首步冒烟验证该地图角色具备 ASC/血量/死亡能力(GA_Hero_Death)完整链路。
1 | 环境: FMapTestSpawner(/ShooterTests/Maps, L_ShooterTest_DeviceProperties) → ULyraHealthComponent → ULyraCharacter |
1.5 边界情况 (1 个用例,含 4 个子状态;DT-05-05 已删)
DT-05 死亡边界情况
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| DT-05-01 | ⚠️ 连续致死 | 角色第一次死亡中 | 死亡过程中再次受到致死伤害 | 状态机不异常(不应从 DeathStarted 重新触发) | 🔴 高 | 🟢 回归(状态机幂等) |
| DT-05-02 | 空中死亡 | 角色在空中 | 空中受到致死伤害,观察下落 | 碰撞禁用后角色穿透地面?或有特殊坠落处理 | 🟡 中 | 🚫 并入 DT-01-03 |
| DT-05-03 | 蹲下中死亡 | 角色蹲伏中 | 蹲伏状态受到致死伤害 | 碰撞体高度是否恢复为默认?还是保持蹲伏碰撞? | 🟡 中 | 🟢 回归(碰撞残留) |
| DT-05-04 | 🖐️ Dash 中死亡 | Dash 过程中 | Dash 途中受到致死伤害 | Dash 立即中断,进入正常死亡流程 | 🟡 中 | 🟠 验收(已决策手工) |
⚠️ DT-05-01 风险:连续致死可能导致 Ability System 异常、双重广播 Elimination 消息
⚠️ DT-05-02 风险:空中死亡禁用碰撞后直接穿透地形,视觉效果异常
⚠️ DT-05-03 风险:蹲伏死亡碰撞体不恢复可能导致重生时卡在矮空间
2. 重生
设计依据
Lyra 重生流程(Lyra 无原地”复活”,Pawn 销毁后由 GameMode 重新生成):
1 | 死亡 → OnDeathFinished → DestroyDueToDeath → UninitAndDestroy (Actor 销毁/隐藏) |
2026-08-02 决策:DT-06/07 由测试显式调用
RequestPlayerRestartNextFrame(PC)驱动重生(走生产 RestartPlayer 链路),不等待 Elimination 经验的重生计时器。
2.1 重生后完整性 (1 个用例,含 6 个子状态)
DT-06 重生后完整性
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| DT-06-01 | ⚠️ 重生后血量重置 | 角色死亡后重生 | 检查当前血量 | 血量为满(MaxHealth),无残留伤害状态 | 🔴 高 | 🟢 回归(状态重置) |
| DT-06-02 | ⚠️ 重生后装备重置 | 角色死亡后重生 | 检查武器/装备槽位 | 装备状态与初次生成时一致(无上一局残留) | 🔴 高 | 🟢 回归(状态重置) |
| DT-06-03 | ⚠️ 重生后技能/GAS 重置 | 角色死亡后重生 | 检查 Ability System 状态 | Ability 标签恢复初始,无残留 Status.Death 标签 | 🔴 高 | 🟢 回归(状态重置) |
| DT-06-04 | ⚠️ 重生后所有移动恢复 | 角色死亡后重生 | 重生后按方向键 + 跳跃/蹲下/Dash | 基础移动和高阶移动全部恢复正常 | 🔴 高 | 🟢 回归(吸收 FR-02) |
| DT-06-05 | ⚠️ 重生后碰撞体恢复 | 角色死亡后重生 | 重生后与其他角色碰撞 | 碰撞体恢复正常,可正常阻挡/被阻挡 | 🔴 高 | 🟢 回归(碰撞恢复) |
| DT-06-06 | 重生后交互恢复 | 角色死亡后重生 | 重生后按开火/换弹/互动键 | 所有交互操作恢复正常 | 🔴 高 | 🟢 回归(交互恢复) |
⚠️ DT-06-01 / DT-06-02 / DT-06-03 风险:重生后如果血量/装备/技能未完全重置,可能出现”死后残留”状态
⚠️ DT-06-04 / DT-06-05 风险:移动冻结/碰撞禁用标志未清除,角色变幽灵
2.2 边界情况 (1 个用例,含 1 个子状态;DT-07-02/03 已删)
DT-07 重生边界情况
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| DT-07-01 | ⚠️ 重生后立即死亡 | 角色刚重生 | 重生瞬间再次受到致死伤害 | 正常进入死亡流程,不卡死 | 🔴 高 | 🟢 回归(重生→死亡时序) |
⚠️ DT-07-01 风险:重生→死亡的快速转换可能导致 Ability System 或 Spawner 状态机未正确重置
🚫 2026-08-02 删除说明:DT-05-05(死亡时被推开,碰撞已禁用前提不成立)、DT-07-02(复活点”安全区域”预期无依据)、DT-07-03(复活无敌帧,Lyra 无此设计)为早期不确定内容,已删除。
3. 散布系统
📦 后续排期(2026-08-02):本次交付不含散布系统,用例保留。
设计依据
Lyra 使用 heat 曲线驱动散布系统,核心流程:
1 | AddSpread() [每射击一次]: |
热量范围由三条曲线 X 轴并集动态决定。初始热量为范围中点(50%),默认 HeatPerShot=1.0,CoolDownRate=2.0/s。
3.1 热量累积与冷却
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| SP-01-01 | 连续射击热量累积 | 装备武器 | 连续射击 10 发,每发后记录 CurrentHeat | 每射击一次 CurrentHeat 增加 HeatPerShot(默认 1.0),热量严格递增 | — | 🔴 高 | 🟢 回归(热量累积) |
| SP-01-02 | 停止射击后冷却 | 热量处于高位 | 停火后等待数秒 | CurrentHeat 以 CooldownRate(默认 2.0/s)递减至热量范围中点 | — | 🔴 高 | 🟢 回归(冷却) |
| SP-01-03 | HeatPerShot=0 无限射击 | 配置 HeatPerShot=0 | 连续射击 20 发 | CurrentHeat 不增加,不过热,散布不扩大 | 除零:Heat=0 导致冷却计算除零 | 🔴 高 | ⚪ 回归·单元(clamp 校验) |
| SP-01-04 | 过热后强制冷却 | 热量接近最大值 | 继续射击触发过热 | 武器无法射击,CurrentHeat 强制冷却至安全值 | 过热状态机异常→永久锁定 | 🟡 中 | 🟢 回归(过热状态机) |
3.2 散布与热量映射
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| SP-02-01 | 热量-散布曲线求值 | 装备武器 | 在热量范围多采样点检查 CurrentSpreadAngle | CurrentSpreadAngle = HeatToSpreadCurve.Eval(CurrentHeat),一一对应 | 曲线求值异常→散布突变 | 🔴 高 | ⚪ 回归·单元(曲线求值) |
| SP-02-02 | 最小热量时最小散布 | 热量降至最低 | 停火至热量稳定后射击 | 实际弹孔散布半径等于 HeatToSpreadCurve 在最小热量处的值 | — | 🟡 中 | 🟢 回归(弹孔实测) |
| SP-02-03 | 最大热量时最大散布 | 热量升至最高 | 连射至过热前射击 | 实际弹孔散布半径等于 HeatToSpreadCurve 在最大热量处的值 | — | 🟡 中 | 🟢 回归(弹孔实测) |
3.3 散布修正乘数
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| SP-03-01 | ⚠️ 瞄准修正 | 开启瞄准 | 瞄准状态下射击 | 散布受 SpreadAngleMultiplier_Aiming 修正,瞬瞄/取消不跳变 | 瞄准乘数未应用→散布不变 | 🔴 高 | 🟢 回归(乘数生效) |
| SP-03-02 | ⚠️ 静止修正 | 角色静止 | 站立不动射击 | 散布受 SpreadAngleMultiplier_StandingStill 修正(默认 1.0x) | 静止奖励未缓动→瞬变 | 🟡 中 | 🟢 回归(乘数生效) |
| SP-03-03 | ⚠️ 蹲下修正 | 角色蹲下 | 蹲下射击 | 散布受 SpreadAngleMultiplier_Crouching 修正(默认 1.0x) | 蹲下乘数未生效 | 🟡 中 | 🟢 回归(乘数生效) |
| SP-03-04 | ⚠️ 跳跃修正 | 角色跳跃/下落 | 跳跃中射击 | 散布受 SpreadAngleMultiplier_JumpingOrFalling 修正(默认 1.0x) | 空中精度过高→跳跃射击过于精准 | 🟡 中 | 🟢 回归(吸收 WF-01-03) |
| SP-03-05 | 多乘数组合 | 多种状态叠加 | 跳跃+瞄准中射击 | 所有乘数相乘:实际散布 = CurrentSpreadAngle × 各乘数之积 | 乘数为负/0→散布消失或反转 | 🔴 高 | 🟢 回归(乘数组合) |
| SP-03-06 | 状态切换过渡 | 状态 A → B | 快速蹲起/收镜/停走 | 乘数平滑过渡(TransitionRate=5.0),散布连续变化 | 过渡过快→散布跳变 | 🟡 中 | 🟢 回归(过渡时序) |
3.4 首发精度
| 编号 | 细分状态 | 前置条件 | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| SP-04-01 | 首发精度关闭 | bAllowFirstShotAccuracy=false | 静止后射第一发 | 散布正常,不受首发精度影响 | — | 🟡 中 | 🟢 回归(首发精度) |
| SP-04-02 | 首发精度开启 | bAllowFirstShotAccuracy=true | 静止到最小散布后射第一发 | 第一发 CurrentSpreadAngleMultiplier=0,完美精度 | — | 🔴 高 | 🟢 回归(首发精度) |
| SP-04-03 | 移动后首发精度失效 | 首发精度开启 | 移动后射击 | 移动破坏精度,第一发也有散布 | 移动后未重置→首发精度残留 | 🔴 高 | 🟢 回归(首发精度) |
3.5 散布恢复冷却延迟
| 编号 | 细分状态 | 配置 SpreadRecoveryCooldownDelay | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| SP-05-01 | 无冷却延迟 | 0.0 | 射击后立即观察冷却 | 射击后立即开始冷却,热量递减 | — | 🟡 中 | 🟢 回归(冷却时序) |
| SP-05-02 | 有冷却延迟 | 1.0 | 射击后等待 0.5 秒再等待 1.5 秒 | 前 0.5 秒不冷却,1.0 秒后开始冷却 | 延迟未生效→冷却提前/推迟 | 🟡 中 | 🟢 回归(冷却时序) |
| SP-05-03 | ⚠️ 冷却延迟为负 | -1.0 | 射击后观察冷却 | 不崩溃,冷却行为符合 clamp 后的设计预期 | 负值导致 Timer 异常 | 🟡 中 | ⚪ 回归·单元(clamp 校验) |
3.6 散布曲线边界值
| 编号 | 细分状态 | 配置 | 测试步骤 | 预期结果 | 风险关注 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|---|
| SP-06-01 | ⚠️ SpreadExponent 低于最小 | SpreadExponent=0 | 射击 | 不崩溃,ClampMin=0.1 生效 | ClampMin 未生效→除零/精度异常 | 🔴 高 | ⚪ 回归·单元(ClampMin 校验) |
| SP-06-02 | SpreadExponent 正常值 | SpreadExponent=1.0 | 射击 | 弹孔在散布范围内均匀分布 | — | 🔴 高 | 🟢 回归(分布实测) |
| SP-06-03 | SpreadExponent 极高值 | SpreadExponent=10.0 | 射击 | 弹孔高度集中中心,散布范围缩小 | — | 🟡 中 | 🟢 回归(分布实测) |
代码实现思路:
1 | 环境: FMapTestSpawner(L_Expanse) → ULyraRangedWeaponInstance |
性能测试 — 测试宪章
测试范围
| # | 测试项 | 说明 |
|---|---|---|
| 1 | 帧率影响 | 30/60/120fps 及不稳定帧率下各系统的表现 |
| 2 | 网络延迟影响 | 50/100/200/500ms 延迟下操作响应的正确性 |
| 3 | 逐帧检测 | Frame time、卡顿检测、内存帧级监控 |
全部自动化测试。通过模拟不同帧率和延迟环境,逐帧采集性能数据。
每个测试用例在 3 张不同地图上各执行一次,共 36 个测试实例。
测试计划
| 测试方式 | 适用用例 |
|---|---|
| 🤖 自动化测试 | 全部 12 个用例 × 3 地图 = 36 个实例 |
🎯 用例目的图例:🔵 冒烟(环境/链路基线,失败优先怀疑测试环境)|🟢 回归(防回归保护)|⚪ 回归·单元(数值/配置校验,降级为单元级断言,不走 PIE)|🟠 验收(功能可用性/视觉手感,开发手测等价物)|🚫 已并入或删除(见《用例分级清单》)
1. 帧率影响
| 编号 | 用例名 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| PF-01 | 30fps 下功能正常 | 锁定帧率为 30fps | 执行移动/射击/切换武器 | 功能正常,帧生成时间 ≤ 33ms | 🟡 中 | 🟢 回归(性能基线) |
| PF-02 | 60fps 下功能正常 | 锁定帧率为 60fps | 执行移动/射击/切换武器 | 功能正常,帧生成时间 ≤ 16ms | 🔴 高 | 🟢 回归(性能基线) |
| PF-03 | 120fps 下功能正常 | 锁定帧率为 120fps | 执行移动/射击/切换武器 | 功能正常,帧生成时间 ≤ 8ms | 🟡 中 | 🟢 回归(性能基线) |
| PF-04 | 不稳定帧率下无卡顿 | 帧率在 30~120fps 间波动 | 持续操作 | 无单帧超过 100ms 的卡顿 | 🟡 中 | 🟢 回归(性能鲁棒性) |
| PF-05 | 帧率骤降后恢复 | 从 60fps 突降至 15fps 再恢复 | 持续操作 | 恢复后功能正常,无残留异常 | 🟡 中 | 🟢 回归(性能鲁棒性) |
2. 网络延迟影响
| 编号 | 用例名 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| PF-06 | 50ms 延迟下操作响应 | 模拟 50ms RTT | 射击/移动 | 客户端操作即时响应,命中判定准确 | 🔴 高 | 🟢 回归(网络性能) |
| PF-07 | 100ms 延迟下操作响应 | 模拟 100ms RTT | 射击/移动 | 客户端操作即时响应,命中判定可接受 | 🔴 高 | 🟢 回归(网络性能) |
| PF-08 | 200ms 延迟下操作响应 | 模拟 200ms RTT | 射击/移动 | 客户端操作即时响应,延迟可感知但功能正常 | 🟡 中 | 🟢 回归(网络性能) |
| PF-09 | 500ms 延迟下操作响应 | 模拟 500ms RTT | 射击/移动 | 功能正常,无断线或操作卡死 | 🟡 中 | 🟢 回归(网络性能) |
| PF-10 | 延迟抖动场景 | 延迟在 50~300ms 间波动 | 持续操作 | 不出现断线、瞬移或状态不一致 | 🟡 中 | 🟢 回归(网络抖动) |
3. 逐帧检测
| 编号 | 用例名 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 用例目的 |
|---|---|---|---|---|---|---|
| PF-11 | 逐帧内存检测 | 稳定 60fps | 运行 5 分钟,每帧记录内存 | 内存无持续增长(无泄漏),帧分配稳定 | 🔴 高 | 🟢 回归(内存门禁) |
| PF-12 | 逐帧帧时间检测 | 稳定 60fps | 运行 5 分钟,每帧记录帧时间 | 平均帧时间 ≤ 16ms,99% 帧 ≤ 30ms | 🔴 高 | 🟢 回归(帧时间门禁) |
探索性测试
⏱ 90 分钟 · 手工 · 以玩家视角自由探索,发现组合场景下的 bug
测试范围
探索时重点关注 跨系统交互 和 边界操作,而非单功能验证。
| 轮次 | 聚焦模块 | 高危可能 |
|---|---|---|
| 1 | 武器 + 控制 + 状态 | 换弹中切换武器 → Dash → 射击;死亡瞬间按射击/换弹 |
| 2 | 控制 + 相机 + HUD | 快速移动+转向时相机是否卡墙;跑射/跳射/蹲射的 HUD 反馈 |
| 3 | 队伍 + 武器 + HUD | 多人同时射击同目标;友军伤害判定;比分板实时更新 |
| 4 | 前端 + 初始化链路 | 快速进出菜单/设置后返回战斗;断线重连后状态恢复 |
| 5 | 状态 + 初始化链路 | 连续死亡重生多次后是否有状态残留;重生后能力是否完整 |
| 6 | 音频 + 武器 + 控制 | 密集音效叠加(多人同时开火+语音+脚步);操作时音频延迟 |
记录方式
发现 bug 时记录:
- 做了什么(一两句话描述操作)
- 实际结果(看到了什么)
- 预期结果(应该是什么)
性能测试(Gauntlet)可行性调研
2026-08-03 产出,来源均为本地引擎/仓库一手资料(见文末”来源”)。供《测试计划》阶段四 PF-01~12 的排期与封板决策引用。
结论摘要
- 本机可行:UE 5.8 自带 Gauntlet 运行时(实验性插件,本项目
LyraGame.Build.cs已依赖并编译通过)与 Gauntlet 自动化壳(AutomationTool)。Ryzen 7 6800H / 16 线程 / 32GB 内存足以单机跑帧率与逐帧检测类用例;网络延迟类用例需双进程(Server + Client),本机亦可。 - 最大障碍是时间:36 个实例(12 用例 × 3 地图),每实例含一次冷启动 + 采样(PF-11/12 要求 5 分钟逐帧),估算全量需要半天以上,8/3 封板当天全量不现实。
- 建议:封板前只跑代表性子集(如 PF-02/PF-12 各 1 地图 + PF-09 1 组延迟),其余标记为”已知缺口/排期外”,在总结报告中明示;或先产出控制器骨架与 1 个冒烟实例,验证管线后按需扩展。
环境与前置条件
| 项 | 现状 | 来源 |
|---|---|---|
| Gauntlet 运行时插件 | Engine\Plugins\Experimental\Gauntlet\(实验性);Source\LyraGame\LyraGame.Build.cs:61 已加 "Gauntlet" 依赖 |
本地源码 |
| 既有控制器 | ULyraTestControllerBootTest / ULyraTestControllerStartEliminationTest(继承引擎 UGauntletTestControllerBootTest),已编译 |
Source\LyraGame\Tests\LyraTestController*.h/.cpp |
| 控制器基类 API | UGauntletTestController:OnInit / OnTick(float) / OnPostMapChange(UWorld*) / GetWorld() / GetFirstPlayerController() / GetCurrentMap() / EndTest(int32) / MarkHeartbeatActive(...);通过命令行 -gauntlet=<控制器类名> 创建 |
Engine\Plugins\Experimental\Gauntlet\Source\Gauntlet\Public\GauntletTestController.h |
| Gauntlet 自动化壳(C#) | Engine\Source\Programs\AutomationTool\Gauntlet\(UnrealTestNode / UnrealSession / UnrealRoleConfiguration 等),经 RunUAT(AutomationTool)驱动多进程与产物采集 |
Engine\Source\Programs\AutomationTool\Gauntlet\Unreal\Base\*.cs |
| 构建目标 | 已有 Development 构建(Binaries\Win64\UnrealEditor-LyraGame.dll);性能测试用 LyraGame(打包/Game 目标)或编辑器目标均可 |
仓库构建产物 |
| 逐帧采集备选 | UE 5.8 AutomatedPerfTesting 插件(Engine\Plugins\Performance\AutomatedPerfTesting,含 GauntletSettings.xml 模板)+ -trace(Unreal Insights .utrace) |
本地引擎 |
PF-01~12 落地映射
| 用例 | 手段 | 判定 |
|---|---|---|
| PF-01/02/03 帧率锁定 30/60/120 | 启动参数或 OnTick 内 t.MaxFPS 控制台命令;每帧用 FApp::GetDeltaTime / FPlatformTime 采样帧时间 |
平均帧时间 ≤ 33/16/8ms |
| PF-04 帧率波动 | OnTick 按预设曲线改 t.MaxFPS(30↔120) |
无单帧 >100ms |
| PF-05 骤降恢复 | 60→15→60 阶梯改 t.MaxFPS,恢复后继续采样 |
恢复后帧时间回落,无残留异常 |
| PF-06~09 延迟 50/100/200/500ms | Gauntlet 双角色(Server + Client)单机双进程;net.PacketSimulationSettings(PktLag)或 -PktLag=<ms> 命令行注入 |
无断线/卡死,操作可完成 |
| PF-10 延迟抖动 | PktLag + PktLagVariance(50~300ms 波动) |
无断线/瞬移/状态不一致 |
| PF-11 逐帧内存 | OnTick 每帧记录 FPlatformMemory::GetStats(),持续 5 分钟写 CSV/JSON;或 -trace 用 Insights 分析 |
无持续增长(线性回归斜率≈0) |
| PF-12 逐帧帧时间 | OnTick 每帧记录帧时间 5 分钟;或 AutomatedPerfTesting 插件 | 平均 ≤16ms,99% ≤30ms |
本机可行性评估
- 帧率类(PF-01~05):单进程即可,改动小(1 个
ULyraTestControllerPerfTest+ 参数化地图/上限/时长)。每实例约 24 分钟(冷启动 + 3060s 采样)。 - 网络类(PF-06~10):Gauntlet 原生支持多角色(
UnrealRoleConfiguration/-NumClients),单机 Server+Client 双进程约 4~8 分钟/实例;延迟注入用引擎自带PktLag,无外部依赖。 - 逐帧类(PF-11/12):5 分钟 × 3 地图 × 2 用例 = 30 分钟起步(不含启动),每实例 6~8 分钟。
- 总耗时估算:36 实例 ≈ 3~6 小时(取决于采样时长与地图加载),封板日(8/3)无法全量。
- 风险点:实验性插件 API 变动(5.8 将 Gauntlet 运行时移入 Experimental 插件);双进程对显存/内存要求(32GB 内存够,GPU 单卡双开可接受);PF-04/05 的”无 100ms 单帧”断言对非独占模式(后台窗口)敏感,建议
-unattended -nosplash -noPause+ 固定窗口模式。
建议(供 07 封板决策)
- 本轮(8/3):实现
ULyraTestControllerPerfTest骨架 + 跑 1 个冒烟实例(如 PF-02 × L_Expanse)验证-gauntlet=与EndTest管线;PF-11/12 用自研 OnTick 采样(比 Insights 集成成本低)。 - 若排期不允许:将 PF-01~12 全部记为”已知缺口(排期外)”,门禁降级为”性能骨架可运行 + 手工性能抽样记录”,并在总结报告写明理由。
- 后续完整执行建议放封板后的回归周期,一次跑一个子集(按地图分组)。
来源
TestDocs/02-专项测试/10-性能测试.md(PF-01~12 用例定义与门禁)Source/LyraGame/Tests/LyraTestControllerBootTest.h/.cpp、LyraTestControllerStartEliminationTest.h/.cpp(既有 Gauntlet 控制器)Source/LyraGame/LyraGame.Build.cs:61(Gauntlet 依赖)UE_5.8\Engine\Plugins\Experimental\Gauntlet\Source\Gauntlet\Public\GauntletTestController.h(控制器 API)UE_5.8\Engine\Source\Programs\AutomationTool\Gauntlet\Unreal\Base\Gauntlet.UnrealTestNode.cs等(Gauntlet 壳)UE_5.8\Engine\Plugins\Performance\AutomatedPerfTesting\(UE5.8 逐帧性能测试插件与 GauntletSettings 模板)
测试实施指南
本文档涵盖测试环境搭建、自动化工具选择、Bug 报告模板。属于实施层面的指导,不涉及具体测试用例。
1. 测试环境搭建
1.1 硬件环境
操作系统 Windows 11 UE 5.8 开发环境。
CPU 8 核 3.2GHz+
GPU AMD核显 16G显存。
内存 32GB UE 硬盘 500GB SSD
1.2 软件环境
| 组件 | 版本/工具 | 用途 |
|---|---|---|
| Unreal Engine | 5.8 | 引擎运行环境 |
| Visual Studio | 2022 | 编译调试 |
| Git | 最新版 | 版本管理 |
| 性能分析工具 | Unreal Insights | 逐帧性能分析 |
| 网络模拟 | UE 内置 Network Profiler | 延迟/丢包模拟 |
1.3 测试场景配置
1 | 客户端配置: |
2. 自动化测试工具选择
2.1 CQTest
适用场景:武器数值、控制系统、初始化链路、前端页面、队伍系统。
| 参考 | 官方文档 |
示例:
1 |
|
2.2 Gauntlet(集成/性能测试)
| 项目 | 说明 |
|---|---|
| 适用场景 | 多进程测试(客户端+服务器)、性能测试、性能逐帧检测 |
| 框架 | UGauntletTestController |
| 优点 | 支持多进程、自动采集性能数据 |
| 参考 | 现有 LyraTestControllerBootTest |
2.3 手工测试
| 项目 | 说明 |
|---|---|
| 适用场景 | 所有需要视觉/手感/听感判断的测试 |
| 覆盖 | HUD、相机系统、动画测试、音频系统、探索性测试 |
2.4 工具选择总结
| 系统 | 推荐工具 | 原因 |
|---|---|---|
| 武器数值 | CQTest | SpawnHelper 轻松生成武器 + 目标,InputTestActions 模拟射击 |
| 武器功能 | CQTest | PIENetworkComponent 验证命中同步 |
| 控制系统(数值) | CQTest | 验证移动速度/跳跃高度/冷却时间 |
| 控制系统(功能) | 手工 | 手感/动画需要人工判断 |
| 相机系统 | 手工 | 平滑度/碰撞感受需视觉判断 |
| HUD | 手工 | 所有元素需视觉判断 |
| 前端页面 | CQTest + Gauntlet | UI 交互流程可脚本化 |
| 音频系统 | 手工 | 听感判断 |
| 队伍系统 | CQTest + Gauntlet | PIENetworkComponent 模拟多玩家 |
| 初始化链路 | CQTest + Gauntlet | 资源加载 + 网络同步验证 |
| 性能测试 | Gauntlet + Unreal Insights | 多进程 + 逐帧分析 |
3. Bug 报告模板
3.1 标准模板
1 | ## Bug 报告 |
3.2 快速模板(适用于探索性测试)
1 | [系统] 一句话描述 |
3.3 严重等级定义
| 等级 | 定义 | 响应 |
|---|---|---|
| 🔴 阻塞 | 游戏崩溃、无法继续、核心功能不可用 | 立即修复 |
| 🟡 严重 | 功能异常但可绕行、数值明显错误 | 当天修复 |
| 🟢 一般 | 边缘 case、UI 显示问题 | 迭代修复 |
| ⚪ 建议 | 体验优化建议 | 排期讨论 |
蓝图功能测试实战:WF-01-01 单发武器射击
⚠️ 已废弃(2026-08 决策):蓝图功能测试已弃用,回归 C++ CQTest(重复性高的用例),其余用手工测试。
复杂逻辑(输入模拟 + 异步等待 + 状态断言)在蓝图中难以编写和维护。
本文档仅作历史参考,不再作为实施指南;WF-01/02 改用Source/LyraGame/Tests/CQTest/WeaponSystem_Fire.spec.cpp(CQTest + InputTestActions)实现。
对应宪章:
TestDocs/01-系统测试/01-武器与装备系统.md
参考资产:Plugins/ShooterTests/Content/Blueprint/B_Test_FireWeapon
参考地图:Plugins/ShooterTests/Content/Maps/L_ShooterTest_FT_SingleShot(已拆分为每个测试独立地图)
前置:打开参考资产
在开始之前,先在编辑器打开 B_Test_FireWeapon 蓝图,边看边做:
- 内容浏览器 → Plugins → ShooterTests → Content → Blueprint →
B_Test_FireWeapon - 右键 → **Run Functional Test…**(先跑一次看看效果)
- 双击打开蓝图,看它的
Event Start Test实现
第一步:创建测试地图
| 操作 | 说明 |
|---|---|
| 文件 → 新建关卡 | 选择 空关卡 |
| 另存为 | Content/FunctionalTests/FT_WeaponTest_Main |
| 打开 World Settings | 工具栏 → 设置 → World Settings |
| Default Gameplay Experience | 设为 B_BasicShooterTest(路径:ShooterTests/Content/System/Experiences/) |
| GameMode Override | 设为 BP_LyraShooterGameMode(项目默认) |
| 保存地图 |
参考
L_ShooterTest_FT_SingleShot的 WorldSettings 配置。所有测试地图都从_Template_ShooterTest模板创建,模板含基础世界设置和共享设施(Floor/Light/PlayerStart/Observation)。
第二步:创建测试蓝图
| 操作 | 说明 |
|---|---|
内容浏览器 → Content/FunctionalTests/ |
右键 → 蓝图类 |
| 父类选择 | 搜索 FunctionalTest |
| 命名 | FT_WF_01_01_SingleShot |
| 打开蓝图编辑器 |
第三步:声明变量
在 MyBlueprint 面板中新增以下变量:
| 变量名 | 类型 | 用途 |
|---|---|---|
TestPawn |
Actor (或 Character) |
缓存玩家 Pawn |
QuickBar |
LyraQuickBarComponent |
缓存快捷栏组件 |
WeaponItem |
LyraInventoryItemInstance |
当前武器物品 |
InitialAmmo |
Integer |
射击前的弹药数 |
DidFire |
Boolean (默认 false) |
标记是否已执行射击,防重复 |
变量都加 Instance Editable(不需要),至少确保它们是 Public 或 Private。
第四步:编写 OnPrepareTest
在事件图表中右键搜索 On Prepare Test 事件,添加:
1 | Event OnPrepareTest: |
关于 GameplayTag:在
Get Stat Tag Stack Count的 Tag 引脚,右键 → “Create a Make Literal GameplayTag” → 填入Lyra.ShooterGame.Weapon.MagazineAmmo
第五步:编写 IsReady
搜索 Is Ready 事件,添加返回值:
1 | Event IsReady (return bool): |
使用 AND 布尔节点 连接所有条件。
第六步:编写 StartTest
搜索 Start Test 事件:
1 | Event StartTest: |
关于 Input Key:需要从 Pawn 获取 Controller,然后 Cast 到
PlayerController,再调用Input Key。
Input Key 节点详解
TestPawn → Get Controller → Cast To PlayerController → Input Key:
| 参数 | 值 |
|---|---|
| Player Controller | 转型后的 PlayerController |
| Key | Left Mouse Button(搜索 “LeftMouseButton”) |
| Event Type | Pressed |
第七步:编译、放置、测试
编译
- 点击 编译(Compile),检查没有节点警告
放置到地图
- 打开
FT_WeaponTest_Main地图 - 把
FT_WF_01_01_SingleShot拖入场景(放在任意位置) - 选中它,在 Details 面板检查:
Time Limit= 30 秒(默认)Times Up Result= Failed
运行测试
方法一:直接运行
- 选中场景中的
FT_WF_01_01_SingleShot→ Details 面板 → Run Test
方法二:通过 Session Frontend
- 菜单 → 工具 → Session Frontend → Automation 标签
- 搜索 “FT_WeaponTest”
- 勾选 → Start Tests
常见问题排查
| 症状 | 原因 | 解决 |
|---|---|---|
| Pawn 为 null | Experience 没加载完 | IsReady 中加 Is Valid 检查,调大到 30 秒 |
| 弹药始终为 0 | GameplayTag 名称不对 | 确认你用的是 Lyra.ShooterGame.Weapon.MagazineAmmo |
| 射击没触发出子弹 | Input Key 没连对 | 检查 Cast 到 PlayerController, 用鼠标左键 |
| 测试超时 Failed | FinishTest 没被调用 |
确保 Delay 后执行了 FinishTest |
| 测试一直 Running | IsReady 永远返回 false |
打印一下各 IsValid 结果 |
扩展:WF-01-02 连发射击
把 Input Key 改成 Hold(Event Type = Released 时标记完成),并且:
StartTest中按射击键不松(Pressed)- 在
Event Tick中检查弹药的消耗量 > 1 - 延迟 1 秒后松开(Released)
- 断言弹药消耗了多发
核心要点总结
1 | FunctionalTest 蓝图 = 一张地图上的裁判决 Actor |
打开项目自带的 L_ShooterTest_FT_SingleShot(或 B_Test_FireWeapon 蓝图)边看边做,它是你最好的老师。注意:原 L_ShooterTest_FireWeapon 地图已拆分为 6 个独立测试地图,每个地图仅含 1 个测试 Actor,避免各测试互相影响。
测试工作流清单
2026-08-03 从《测试计划》§5.5 抽出独立成文:执行按分级、管理按模块的具体落地。
按触发时机组织为可打勾的工作清单,每个事件附具体用例索引,可照着直接执行。
用例编号统一遵循各系统宪章(EQ/WF/WS/MV/AM/NV/CM/HD/FE/AU/TM/IN/DT/SP/PF)。
使用方式
遇到下列任一触发事件时,打开对应清单逐项执行;未打勾的项就是当前阻塞。
每个事件的”本事件用例”即该时机需要执行的具体用例,来源为各系统宪章。
事件 A:每次代码提交(开发)
- 运行本次改动涉及的 ⚪ 回归·单元(数值 / 配置 / clamp 校验,可挂 pre-commit)
- 判断改动影响哪些模块 → 对照对应系统宪章
- 开发自测 / 一次性验证完成(个人工具,不进宪章)
本事件用例(⚪ 回归·单元)
- 武器数值:WS-01(距离衰减曲线)、WS-02(射速配置)、WS-03(弹药量)、WS-04-01/02/04(部位伤害倍率校验)
- 控制数值:NV-01-01/02(跳跃速度 clamp)、NV-03-01/02(重力 clamp)、NV-05-01/02(MaxWalkSpeed clamp)
- 相机数值:NV-01(FOV clamp)、NV-02(俯仰限幅)
- 音频数值:NV-01(音量边界)、NV-02(通道独立)
- 事件数值:SP-01-03(HeatPerShot=0)、SP-02-01(曲线求值)、SP-05-03(冷却延迟负值)、SP-06-01(SpreadExponent=0)
事件 B:每次合并 / 构建(CI)
- 🔵 冒烟全绿(环境 / 链路基线:地图加载、Pawn 生成、输入链路)
- 失败 → 停止后续,先修环境或基础链路,回归结果不作数
- ⚪ 回归·单元全量(随构建)
- 🟢 回归全量(历史用例)
- 失败 → 区分:功能被改坏(修代码)还是断言过时(更新用例)
本事件用例 — 冒烟(🔵)
- 初始化链路:IN-01-01/02(进程启动 / Experience 加载)、IN-02-01~03(Pawn / AbilitySet / 默认武器)、IN-04-01/02(输入绑定)
- 前端页面:FE-02-01/02(主菜单 → 进入对局)、FE-05-01/02(加载链路)
- 武器:EQ-01(初始武器)、WF-01-01(单发射击)
- 控制:MV-01(方向移动)、AM-01-01(平地跳跃)
本事件用例 — 回归(🟢,全量)
| 模块 | 用例 |
|---|---|
| 武器 | EQ-02-01 |
| 控制 | MV-01/-03、AM-01-01/-02/-04/-05、AM-02-01~04 |
| 前端 | FE-02-03~05、FE-03、FE-04、FE-05-03、FE-06 |
| 初始化 | IN-01-03、IN-02-04~06、IN-03、IN-05、NV-01 |
| 事件 | DT-01、DT-03、DT-05-01/-03、DT-06、DT-07-01、SP-01-01/-02/-04、SP-02-02/-03、SP-03、SP-04、SP-05-01/-02、SP-06-02/-03 |
注:控制 NV 的中间值/最大值断言(NV-01-03/04 跳高、NV-03-03/04 下落、NV-05-03/04 速度)已并入 AM-01 / MV-01 实测,不单独执行。
事件 C:里程碑 / 版本候选
- 逐条过《测试计划》§6 质量门禁
- 🟠 验收 Session 1~3(按模块打包执行)
- Session 1 核心视觉:武器动画 → 装备动作
- Session 2 移动体验:控制动画 → 视角 → 相机
- Session 3 UI 与信息:HUD → 队伍 → 音频
- 手工发现 Bug 全部登记到
TestDocs/Bug报告/ - 🔴 阻塞 Bug 清零
- 视觉 / 手感问题决策:修复 or 记为已知问题
本事件用例 — 验收(🟠,手工)
- Session 1 核心视觉:武器 WA-01
03(装备 / 换弹 / 射击动画)、EQ-0105(装备动作手感) - Session 2 移动体验:控制 AN-01-01
05(移动动画)、LC-01-0103/-05(视角)、AM-02-05(矮通道)、AM-03-01~06(Dash);相机 CM-01/02/05(跟随 / 碰撞 / 蹲下偏移) - Session 3 UI 与信息:HUD HD-01
05;队伍 TM-0105;音频 AU-01~04;事件 DT-02-02(重生后摄像机恢复)、DT-05-04(Dash 中死亡)
事件 D:发布前(最后一关)
- 功能冻结确认(探索期间不再改功能)
- 🟣 探索性测试 6 轮 × 90 分钟(跨系统组合 / 边界操作)
- 每轮结束做回流决策:可复现且值得保护 → 固化为宪章用例(标 🟢 / 🟠);一次性 → 留 Bug 报告
- 探索期间若有改动 → 🟢 回归全量复跑
- 性能门禁(PF-01~12)通过
- 发布决策:无 🔴 阻塞 + 已知问题可接受
本事件用例 — 探索(🟣)
- 6 轮聚焦模块见探索性测试宪章:武器+控制+状态 / 控制+相机+HUD / 队伍+武器+HUD / 前端+初始化链路 / 状态+初始化链路 / 音频+武器+控制
- 每轮结束回流决策:可复现且值得保护 → 固化为宪章用例(标 🟢 / 🟠);一次性 → 留 Bug 报告
本事件用例 — 性能(🟢)
- PF-01
03(30 / 60 / 120fps 功能与帧时间)、PF-04/05(帧率波动 / 骤降恢复)、PF-0610(50~500ms 延迟与抖动)、PF-11/12(内存 / 帧时间门禁)——见性能测试宪章
分级执行矩阵(速查)
| 分级 | 触发时机 | 频率 | 谁执行 | 失败意味着 |
|---|---|---|---|---|
| ⚪ 回归·单元 | 每次提交 / 构建 | 最高,可进 pre-commit | 开发 | 数据 / 配置被改坏 |
| 🔵 冒烟 | 每次合并后 | 每次构建 / 合并 | CI | 环境或基础链路断了 |
| 🟢 回归 | 每次合并 / 里程碑 | 每天 ~ 每周 | CI | 功能被改坏 |
| 🟠 验收 | 发布前 | 每个里程碑 | QA | 体验不达标,不能发版 |
| 🟣 探索 | 验收通过后 | 每个发布前 | QA | 发现未知组合问题,需回流 |
与其它文档的关系
| 文档 | 回答的问题 |
|---|---|
| 各系统宪章 | 测什么(用例归属:模块) |
| 《测试计划》 | 按什么顺序实现、质量门禁是什么 |
| 《用例分级清单》 | 每个用例为什么存在(目的) |
| 本清单 | 什么时机做什么动作(流程:打勾执行) |
Bug 报告
标题: [控制系统] AM-03-05 Dash 中操作行为不一致(蹲下排队延迟执行 / 切换装备打断 Dash / 射击·跳跃·近战·手雷无效)
等级: 🟡 严重
环境: 单机 · 手工测试(Dash 为蓝图 GA,见宪章 AM-03 决策)· 不限帧率
步骤:
- 角色正常站立,按 Dash 键触发冲刺(
GA_Hero_Dash) - Dash 过程中依次输入:蹲下键、切换装备键(QuickBar)、射击键、跳跃键、近战键、手雷键
- 分别观察各输入在 Dash 过程中的表现,以及 Dash 结束后的后续状态
实际: 同类”Dash 中操作”输入被以三种不同策略处理,行为互相矛盾:
| 输入 | 实际表现 |
|---|---|
| 蹲下 | 排队:Dash 过程中不生效,Dash 结束后角色自动蹲下(延迟执行) |
| 切换装备 | 打断:直接中止 Dash,冲刺被打断 |
| 射击 / 跳跃 / 近战 / 手雷 | 忽略:Dash 过程中输入完全无效,Dash 结束后也不补执行 |
预期: Dash 期间的操作策略应遵循统一的设计语言,而非三种策略混用。参照宪章 AM-03-05 预期”Dash 过程中不能进行其他操作(或可提前取消)”,应明确并统一为以下策略之一:
- 方案 A:Dash 期间所有输入一律忽略(与现有射击/跳跃/近战/手雷表现一致,蹲下与切装也应忽略)
- 方案 B:Dash 期间所有输入一律排队,Dash 结束后按序执行(与现有蹲下表现一致)
- 方案 C:Dash 期间所有输入一律打断 Dash(与现有切换装备表现一致)
关联: 宪章 AM-03-05(Dash 中操作)/ GA_Hero_Dash(蓝图 GA)/ IA_Ability_Dash
推测: Dash 为蓝图 GA(Plugins/GameFeatures/ShooterCore/Content/Game/Dash/GA_Hero_Dash),输入经 Lyra GAS 输入标签分发(ULyraAbilitySystemComponent::ProcessAbilityInput),三种行为很可能来自输入策略差异:
- 蹲下为按住保持类输入(WhileInputActive / Held 输入),Dash 激活期间被 BlockTags 阻止激活,但 held 输入状态在
InputHeldSpecHandles中持续保留,Dash 结束、BlockTags 移除后即被激活 → 表现为”排队,Dash 结束后蹲下”; - 切换装备走 QuickBar/装备流程,不属于被 BlockTags 阻止的 GA 激活路径,直接取消了 Dash → 表现为”打断”;
- 射击/跳跃/近战/手雷为一次性触发输入(OnInputTriggered),Dash 期间被 BlockTags 拒绝后输入即丢失、不保留 → 表现为”完全无效”。
建议:在 Dash GA(或 AbilityTagRelationshipMapping)中统一 Dash 期间的输入语义(全部忽略 / 全部排队 / 全部打断),并对被拒绝的触发类输入做丢弃或排队策略的一致处理。
Bug 报告
标题: [控制系统] NV-03-02 GravityScale=0 悬浮时 ABP_Mannequin_Base 动画蓝图除零
等级: 🟡 中(非阻塞 · LogScript Warning,未崩溃)
环境: 单机 · L_Expanse · CQTest(无人值守低帧率)
步骤:
- 运行 CQTest
NV_03_02_GravityZero(ControlSystem.spec.cpp) - 测试设置
CharacterMovement->GravityScale = 0后跳跃 - 角色以恒定 Z 速度悬浮(无重力不下落)
实际: ABP_Mannequin_Base::UpdateJumpFallData 每帧输出两次 Divide by zero: Divide_DoubleDouble(LogScript Warning,调用栈:UpdateJumpFallData ← BlueprintThreadSafeUpdateAnimation)。物理引擎未除零、角色正常悬浮、无崩溃。
预期: 角色悬浮(垂直速度恒定)时动画蓝图不应除零。宪章 NV-03-02 风险关注为”除零:物理引擎除零崩溃”——实测物理引擎安全,但动画蓝图层存在除零,属于次要系统对极端输入(无重力悬浮)不健壮。
关联: 宪章 NV-03-02(重力参数验证 a 最小值) / ControlSystem.spec.cpp
推测: UpdateJumpFallData 内存在基于”跳跃高度差/时间差”等量的除法;GravityScale=0 时角色 Z 速度恒定(不衰减),某些差值计算为 0,导致除数 0。建议在动画蓝图中对除数为 0 的情况做保护(如 SafeDivide 或 clamp),或在计算前判断垂直速度是否变化。
Bug 报告
标题: [武器] WF-02-04 射击途中换弹被静默拒绝
等级: 🟡 严重
环境: 单机 · FT_WeaponTest_Main · 不限帧率
步骤:
- 持有步枪,按住射击键持续开火
- 保持射击的同时按换弹键
实际: 换弹指令被忽略,射击继续正常进行,无换弹动画、弹药不恢复,停止射击换弹动作不执行
预期: 射击被打断→执行换弹(依测试宪章 WF-02-04 定义)
关联: WF-02-01 / WF-02-05
推测: GA_Weapon_Fire 处于 Active 状态时阻挡了 GA_Weapon_Reload_Rifle 的激活请求。换弹 Ability 应具有更高优先级以中断射击。
Bug 报告
标题: [武器] 装备时武器先出现在手中,后播放拿出动画(mesh 显示与 Equip 动画时序错位)
等级: 🟢 一般
环境: 单机 · 武器测试场景(FT_WeaponTest_Main / L_Expanse)· 不限帧率
步骤:
- 获得武器(
AddItemDefinition(ID_Rifle)加入背包) - 装备武器(QuickBar 槽位设为 active,触发 Equip)
- 观察装备瞬间的角色动画与武器 mesh 显示
实际: 装备瞬间武器 mesh 立即出现在角色手中 → 之后才播放”拿出武器”动画(Equip montage)→ 动画播放期间武器从手中消失 → 动画播完后武器再次出现。整体呈现”先出现 → 消失 → 再出现”的闪烁。
预期: 先播放拿出动画,动画播放到”武器被握住”的节点时武器 mesh 才显示,之后持续保持在手中(一次性出现,无闪烁)。
附加材料:
- 录像:
/Game/Cinematics/Takes/Takes_20260801/Scene_1_10(Take Recorder 录制,可回放观察时序)
关联测试用例:
- WA-01(武器动画 · 装备武器动画:拾取或切换到武器 → 切换/装备动画播放,角色正确持枪)
- 该缺陷直接导致 WA-01 预期”角色正确持枪”在装备瞬间不成立(武器先显示→消失→再出现)
推测: 装备流程中 ULyraEquipmentInstance 的装备 actor(B_Pistol / B_Shotgun 等)在 EquipItem 时 mesh 立即设为可见,而 Equip montage(拿出动画)的播放与”武器 attach 到手上”的时机在其后。缺少同步机制:montage 开始前应隐藏 mesh(或延迟 spawn/attach),并在 montage 的动画 Notify(如”握住武器”节点)触发时才显示 mesh。
Lyra 测试项目执行计划
开始日期:2026-07-11
📌 封板收尾(2026-08-04):自动化 CQTest 96/96 全绿(7 套件)、手工 Session 1~3 完成(2026-08-03)、
无 🔴 阻塞 Bug → 按”功能封板”口径封板(2026-08-03)。性能 36 实例 / 探索性 6 轮 /
DT-04 网络 / SP 剩余子状态列入《已知缺口 / 封板后 backlog》,详见《Lyra 测试总结报告》。
阶段一:框架搭建(7/12 – 7/14,3天)
目标:基建跑通,验证测试工具链(CQTest / PowerShell)可用。
2026-08 决策:蓝图功能测试已弃用,原 Day 1 蓝图任务改为 CQTest 武器功能验证。
Day 1 — 7/12(周六)
| 顺序 | 任务 | 工具 | 预计 |
|---|---|---|---|
| 1.1 | 阅读 WeaponSystem_Equipment.spec.cpp,理解 CQTest + FMapTestSpawner 标准模式 |
VS | 30min |
| 1.2 | 新建 WeaponSystem_Fire.spec.cpp,实现 WF-01-01:单发射击 → 弹药 -1 |
CQTest | 1.5h |
| 1.3 | 编译 → Session Frontend 运行 CQTest | VS + UE | 15min |
| 1.4 | 截图结果(通过/失败均可) | — | 5min |
| 1.5 | 如果失败:排查报错,修复 → 重新跑 → 记录到 Bug报告/ | CQTest | 1h |
| 1.6 | 如果通过:扩展为 WF-01-02 连发射击测试(InputTestActions 按住) | CQTest | 30min |
Day 1 完成标准:至少一个武器功能 CQTest 跑通并有截图。
Day 2 — 7/13(周日)
| 顺序 | 任务 | 工具 | 预计 |
|---|---|---|---|
| 2.1 | 打开 ShooterMapsInit.spec.cpp,逐行理解现有代码 |
VS | 30min |
| 2.2 | 补全 IN-01:游戏进程启动校验 | CQTest | 30min |
| 2.3 | 补全 IN-02:Experience + GameMode 初始化校验 | CQTest | 1h |
| 2.4 | 在 VS 中编译 → 通过 Session Frontend 运行 CQTest | VS + UE | 30min |
| 2.5 | 截图全部通过的结果 | — | 5min |
Day 2 完成标准:IN-01 ~ IN-02 CQTest 编译通过并执行成功。
Day 3 — 7/14(周一)
| 顺序 | 任务 | 工具 | 预计 |
|---|---|---|---|
| 3.1 | 新增 IN-03:网络同步就绪(GameState 复制校验) | CQTest | 45min |
| 3.2 | 新增 IN-04:Pawn 资源加载(Class存在 + 生成无报错) | CQTest | 30min |
| 3.3 | 新增 IN-05:AbilitySet 资源(GrantedAbilities 注册校验) | CQTest | 45min |
| 3.4 | 编译 → 全部通过 | VS + UE | 20min |
| 3.6 | 运行 RunInitChainTests.ps1 → 验证一键执行流程 |
PowerShell | 30min |
Day 3 完成标准:InitChain CQTest 全部通过 + PowerShell 脚本一键执行成功。
🔒 阶段一门禁(必须全部打勾才能进入阶段二)
- 武器功能 CQTest(WF-01-01 单发射击)跑通(截图)
- CQTest 初始化链路 5 个主用例全部通过(截图)
- RunInitChainTests.ps1 一键执行无报错
- 前端 Spec 至少完成 1 个用例
阶段二:核心自动化(7/15 – 7/20,6天)
目标:拿起武器 → 移动操作 → 射击 → 伤害 → 死亡,全链路可脚本化验证。
Day 4 — 7/15(周二)
| 顺序 | 任务 | 工具 |
|---|---|---|
| 4.1 | 新建 WeaponSystem_Fire.spec.cpp |
CQTest |
| 4.2 | 实现 WF-01:单发武器射击验证(按一下 → 出一发子弹 → 弹药-1) | CQTest |
| 4.3 | 实现 WF-02:连发武器射击验证(按住 → 持续出弹 → 松开停止) | CQTest |
Day 5 — 7/16(周三)
| 顺序 | 任务 | 工具 |
|---|---|---|
| 5.1 | 实现 WS-01:散布值验证(固定位置 → 射击100发 → 计算散布半径) | CQTest |
| 5.2 | 实现 WS-02/03/04:距离衰减(0/负值/正常 → 不同距离伤害对比) | CQTest |
Day 6 — 7/17(周四)
| 顺序 | 任务 | 工具 |
|---|---|---|
| 6.1 | 实现 WS-05/06/07:射速边界值(0/负值/正常) | CQTest |
| 6.2 | 实现 WS-08/09/10:弹药量边界值(0/负值/正常) | CQTest |
| 6.3 | 实现 WS-11/12/13:部位伤害(头/身体/四肢) | CQTest |
Day 7 — 7/18(周五)
| 顺序 | 任务 | 工具 |
|---|---|---|
| 7.1 | 补全 ControlSystem.spec.cpp(已有文件):MV-01~03 对齐宪章编号重命名 |
CQTest |
| 7.2 | 实现 AM-01(跳跃含连续跳跃 -04)、AM-02 蹲下(-01~-04) | CQTest |
| 7.3 | AM-03 Dash、AM-02-05 矮通道 → 转手工(蓝图 GA,见宪章) | 手工 |
| 7.4 | 编译 → 运行 Project.ControlSystem 验证 | CQTest |
Day 8 — 7/19(周六)
| 顺序 | 任务 | 工具 |
|---|---|---|
| 8.1 | NV-01 跳跃速度:补 a-ε(-0.01) / mid(210)(0、420 已有) | CQTest |
| 8.2 | NV-03 重力:补 a-ε(-0.01) / mid(5) / max(10)(0 已有) | CQTest |
| 8.3 | NV-05 MaxWalkSpeed 边界值(-1/0/300/600)+ FR-01/02 死亡冻结回填 | CQTest |
| 8.4 | NV-02 Dash 冷却、NV-04 灵敏度 → 转手工(蓝图 GA / 鼠标手感) | 手工 |
Day 9 — 7/20(周日)🚫 已砍(2026-08-02 决策:状态系统不需要)
| 顺序 | 任务 | 工具 |
|---|---|---|
StatusSystem.spec.cpp |
||
🔒 阶段二门禁
- 武器自动化全部通过(EQ/WS 走 CQTest;WF-01~02 已用蓝图功能测试完成,2026-08-03 决策:与宪章 CQTest 不符,先按蓝图执行)
- 控制 CQTest 全部通过(MV-01
03、AM-0102、NV-01/03/05、FR-01/02) 状态 CQTest 全部通过(4主用例)(已砍,2026-08-02 决策)- 边界值(0/负/正常)全部覆盖
- 无 🔴 阻塞 Bug
阶段二点五:死亡事件与网络(7/21 – 7/24,4天)
目标:死亡/复活完整链路 + PIE 多开验证状态复制。
Day 10 — 7/21(周一)
| 顺序 | 任务 | 工具 |
|---|---|---|
| 10.1 | 新建 EventSystem_Death.spec.cpp |
CQTest |
| 10.2 | 实现 DT-01:血量归零触发死亡(DeathState 状态机验证) | CQTest |
| 10.3 | 实现 DT-02:死亡后移动/射击/操作冻结 | CQTest |
| 10.4 | 实现 DT-03:死亡后碰撞体变化(不再被射击命中) | CQTest |
Day 11 — 7/22(周二)
| 顺序 | 任务 | 工具 |
|---|---|---|
| 11.1 | 实现 DT-04:PIE 双开,验证 DeathState 在客户端—服务端复制一致 | CQTest + PIENetwork |
| 11.2 | 实现 DT-05:死亡瞬间输入排队(死亡时按射击 → 复活后不会自动开火) | CQTest |
Day 12 — 7/23(周三)
| 顺序 | 任务 | 工具 |
|---|---|---|
| 12.1 | 实现 DT-06:复活链路(血量/装备/技能重置完整性) | CQTest |
| 12.2 | 实现 DT-07:复活边界情况(死亡后立即重生 / 重生后立即死亡) | CQTest |
Day 13 — 7/24(周四)缓冲日
| 顺序 | 任务 |
|---|---|
| 13.1 | 阶段一~二点五 遗留 Bug 修复 |
| 13.2 | 更新执行计划文档,记录实际完成情况 |
🔒 阶段二点五门禁
- 死亡单机(DT-01~05)全部通过
- 死亡网络(DT-04)通过
- 复活(DT-06~07)全部通过
- 无 🔴 阻塞 Bug
阶段三:边界值 + 手工测试(7/25 – 7/28,4天)
目标:补完数值边界自动化 + 执行全部手工用例。
Day 14 — 7/25(周五)
| 顺序 | 任务 | 工具 |
|---|---|---|
| 14.1 | 新建 CameraSystem_Numeric.spec.cpp → NV-01/02(FOV 0/负/正常 + 俯仰限幅) |
CQTest ✅ 9/9 |
| 14.2 | 新建 AudioSystem_Numeric.spec.cpp → NV-01/02(音量边界) |
CQTest ✅ 8/8 |
TeamSystem_Numeric.spec.cpp → NV-01(队伍数量边界) |
||
| 14.4 | 全部编译通过 | VS + UE |
Day 15 — 7/26(周六):手工 Session 1
| 顺序 | 任务 | 方式 |
|---|---|---|
| 15.1 | 武器动画 WA-01~03(装备/换弹/射击) | 肉眼 + OBS录屏 ✅ |
| 15.2 | 装备动作 EQ-01~05(切换/空槽/满槽/冷却) | 肉眼 + OBS录屏 ✅ |
| 15.4 | 发现的 Bug 写入 Bug报告/ |
模板 ✅(装备动画 / WF-02-04) |
Day 16 — 7/27(周日):手工 Session 2
| 顺序 | 任务 | 方式 |
|---|---|---|
| 16.1 | 控制动画 AN-01(移动/跳跃/蹲下/Dash/切换) | 肉眼 + OBS录屏 ✅ |
| 16.2 | 视角 LC-01(鼠标水平/垂直/反转/灵敏度) | 肉眼 ✅ |
| 16.3 | 相机 CM-01/02/05(跟随/碰撞/蹲下偏移) | 肉眼 + OBS录屏 ✅ |
| 16.4 | 发现的 Bug 写入 Bug报告/ |
模板 ✅(AM-03-05) |
Day 17 — 7/28(周一):手工 Session 3
| 顺序 | 任务 | 方式 |
|---|---|---|
| 17.1 | HUD HD-01~05(准星/命中标记/伤害数字/布局/血条弹药) | 肉眼 + 截图 ✅ |
| 17.2 | 队伍 TM-01~05(归属/识别/伤害/显示/比分板) | 肉眼 ✅ |
| 17.3 | 音频 AU-01~04(音量/输出/HDR/加载) | 耳朵 + 录屏 ✅ |
| 17.4 | 发现的 Bug 写入 Bug报告/ |
模板 ✅(无新增) |
🔒 阶段三门禁
- 相机/音频 边界值 CQTest 全部通过(9/9、8/8;队伍 NV-01 已删,2026-08-03)
- 手工 Session 1~3 全部执行完毕(2026-08-03)
- Bug报告/ 有实际内容(4 份)
- 无 🔴 阻塞 Bug
阶段四:压测与探索(7/29 – 8/3,6天)
目标:性能边界验证 + 跨系统组合 Bug 发现。
Day 18 — 7/29(周二)
| 顺序 | 任务 | 工具 |
|---|---|---|
| 18.1 | 实现 PF-01/02/03:30fps / 60fps / 120fps 帧率锁定测试 | Gauntlet |
| 18.2 | 实现 PF-04:帧率 30~120fps 波动 → 无单帧超过 100ms 卡顿 | Gauntlet |
| 18.3 | 实现 PF-05:帧率骤降恢复(60→15→60) | Gauntlet |
Day 19 — 7/30(周三)
| 顺序 | 任务 | 工具 |
|---|---|---|
| 19.1 | 实现 PF-06/07:50ms / 100ms 延迟射击验证 | Gauntlet |
| 19.2 | 实现 PF-08/09:200ms / 500ms 延迟射击验证 | Gauntlet |
| 19.3 | 实现 PF-10:延迟 50~300ms 抖动 → 无断线 | Gauntlet |
Day 20 — 7/31(周四)
| 顺序 | 任务 | 工具 |
|---|---|---|
| 20.1 | 实现 PF-11:逐帧内存检测(5分钟,每帧记录) | Gauntlet + Insights |
| 20.2 | 实现 PF-12:逐帧帧时间检测(5分钟,99%帧≤30ms) | Gauntlet + Insights |
| 20.3 | 所有性能数据导出 → 写入测试报告 | — |
Day 21 — 8/1(周五):探索性测试 前半
| 轮次 | 聚焦 | 方式 | 时长 |
|---|---|---|---|
| 第1轮 | 武器 + 控制 + 状态(换弹→Dash→射击;死亡瞬间操作) | 自由探索 + OBS | 90min |
| 第2轮 | 控制 + 相机 + HUD(快速转向卡墙;跑射/跳射/蹲射HUD) | 自由探索 + OBS | 90min |
| 第3轮 | 队伍 + 武器 + HUD(多人同目标射击;友伤;比分板) | 自由探索 + OBS | 90min |
Day 22 — 8/2(周六):探索性测试 后半
| 轮次 | 聚焦 | 方式 | 时长 |
|---|---|---|---|
| 第4轮 | 前端 + 初始化链路(快速进出菜单;断线重连状态) | 自由探索 + OBS | 90min |
| 第5轮 | 状态 + 初始化链路(连续死亡重生残留;重生能力完整) | 自由探索 + OBS | 90min |
| 第6轮 | 音频 + 武器 + 控制(密集音效叠加;音频延迟) | 自由探索 + OBS | 90min |
Day 23 — 8/3(周日):收尾
| 顺序 | 任务 |
|---|---|
| 23.1 | 全部 Bug 清零或标记为已知问题 ✅(4 份标记,无 🔴 阻塞) |
| 23.2 | 更新测试总纲:标记全部用例最终状态 ✅(2026-08-04) |
| 23.3 | 写一份《Lyra 测试总结报告》(1-2页,汇总发现) ✅(20-测试总结报告.md) |
| 23.4 | 项目封板 ✅(2026-08-04,功能封板口径) |
🔒 阶段四门禁
- 性能测试 36 实例全部通过(➡️ 已知缺口:本机需 3~6h,见《性能测试可行性调研》)
- 60fps 平均帧时间 ≤ 16ms,99% ≤ 30ms(➡️ 同上)
- 延迟 50~500ms 无断线或卡死(➡️ 并入 DT-04 网络 backlog)
- 5min 帧内存无持续增长(➡️ 同上)
- 探索性 6 轮完成,Bug 清零(➡️ 已知缺口,封板后 backlog)
每周检查点
| 日期 | 里程碑 |
|---|---|
| 7/14(周一) | ✅ 三种工具链全通,InitChain 自动化完成 |
| 7/20(周日) | ✅ 核心玩法闭环自动化完成 |
| 7/24(周四) | ✅ 死亡/复活/网络同步完成 |
| 7/28(周一) | ✅ 手工测试全部完成 |
| 8/3(周日) | ✅ 项目封板 |
每天工作流程
1 | 1. 打开本文件,看当天任务 |
如果某天没完成
1 | 不追进度。不压缩后面的任务来补。 |
项目封板后 → 投简历
项目完成日(8/3)开始投递,不提前。


