chore: 自动测试重构及规范
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
# AIBIS Dream Unity Test 规范
|
||||
|
||||
## 1. 目标
|
||||
|
||||
Unity Test 用来快速发现问题、保护已经确认的系统契约和高风险回归。可以为开发中的功能编写阶段性自动测试,但它们必须与长期测试隔离,并在达到删除或转正条件时处理。代码修改不要求机械地新增测试。
|
||||
|
||||
测试套件应满足三个目标:
|
||||
|
||||
1. 失败时能指出一个明确的玩家行为、数据兼容性或系统边界问题。
|
||||
2. 重构实现但不改变行为时,大部分测试无需修改。
|
||||
3. 开发者能在合理时间内运行完整 EditMode 套件,并愿意经常运行它。
|
||||
|
||||
## 2. 何时应该添加测试
|
||||
|
||||
满足以下任一项时,适合添加或扩展长期 Unity Test:
|
||||
|
||||
- 已验收、会长期存在的业务规则或公共 API 契约。
|
||||
- 存档格式、Addressable 地址、资源导入等一旦破坏就会造成严重后果的边界。
|
||||
- 曾经发生且很可能复发的缺陷;测试应复现用户可观察的错误。
|
||||
- 复杂纯逻辑存在清晰输入输出,测试比场景手测更快、更可靠。
|
||||
- 必须由 Unity 生命周期、Timeline、协程或真实组件组合才能验证的关键集成。
|
||||
|
||||
以下测试不应直接进入长期测试套件:
|
||||
|
||||
- 功能仍处于方案探索、中间阶段或尚未验收;如确有自动验证价值,应进入临时测试目录。
|
||||
- 仅调整编辑器窗口布局、按钮位置、颜色、动效参数或调试展示。
|
||||
- 私有方法、字段赋值、属性包装器等实现细节。
|
||||
- 同一行为已经由更低层、更快的测试覆盖。
|
||||
- 为每个枚举值、边界值机械复制一个测试,而这些输入属于同一等价类。
|
||||
- 资源内容完整性扫描。此类检查优先放到专用 Validator、导入检查或发布前检查中。
|
||||
- 只服务开发期的临时工具、测试存档、实验性入口;此类测试应随对应功能一起删除,或在工具转正时重新评审。
|
||||
|
||||
代码发生变化本身不是添加测试的理由。没有满足上述条件时,可以不改测试。
|
||||
|
||||
### 2.1 临时自动测试
|
||||
|
||||
开发中的复杂功能可以使用临时自动测试辅助迭代。临时并不代表低质量:它们仍需可重复运行、可靠清理状态,并选择合适的测试层级。
|
||||
|
||||
临时测试必须满足:
|
||||
|
||||
- 放在 `Assets/Tests/_Temporary/<Feature>/`,不能混入长期领域套件。
|
||||
- 目录内包含简短说明,写明用途、负责人、创建日期以及删除或转正条件。
|
||||
- 测试名称或断言可以面向当前阶段,但不得迫使生产代码暴露只为测试服务的公共 API。
|
||||
- 功能验收、方案替换、功能放弃或已有长期契约测试覆盖时,删除整个临时目录。
|
||||
- 若测试发现了需要长期保护的契约,只将最小必要用例整理后迁入正式领域套件,其余删除。
|
||||
- 评审和功能验收时必须检查 `_Temporary`;不允许没有清理条件的临时测试长期滞留。
|
||||
|
||||
## 3. 测试分层
|
||||
|
||||
一个行为默认只选择能够完整覆盖它的最低测试层级,不能按“纯逻辑一份、组件一份、场景再一份”的方式机械叠加。只有上层测试覆盖了下层无法发现的独立风险时,才允许跨层重复。
|
||||
|
||||
### 3.1 EditMode 逻辑契约
|
||||
|
||||
首选层级。用于解析、状态机、序列化 DTO、规则计算和错误处理。应尽量不加载场景、不等待帧、不依赖真实时间。
|
||||
|
||||
### 3.2 EditMode 资源集成
|
||||
|
||||
只保留少量端到端路径,例如“源文件 -> 导入服务 -> 可播放资源”或“Prefab -> Addressable 地址 -> 运行时组件”。不要逐个测试编辑器操作步骤和窗口状态。
|
||||
|
||||
### 3.3 PlayMode 关键集成
|
||||
|
||||
仅当行为依赖 `OnEnable` / `OnDisable`、协程、`PlayableDirector`、物理或真实帧推进时使用。能通过显式求值器或纯逻辑 EditMode 测试验证的行为,不应再写一份 PlayMode 测试。
|
||||
|
||||
原则上,每个系统只保留少数关键 PlayMode 主链。新增 PlayMode 测试时,需要说明为什么 EditMode 无法覆盖。
|
||||
|
||||
## 4. 目录与程序集
|
||||
|
||||
- 默认 `Assembly-CSharp` 中运行时代码的测试放在 `Assets/Editor/Tests/`。这是因为 asmdef 测试程序集不能直接依赖 Unity 的预定义运行时程序集。
|
||||
- 已有独立 asmdef 的系统放在 `Assets/Tests/<System>/EditMode/` 或 `PlayMode/`。
|
||||
- 临时测试统一从 `Assets/Tests/_Temporary/<Feature>/` 开始组织。默认程序集的临时 EditMode 测试放入其 `Editor/` 子目录;独立程序集测试可在功能目录中建立最小临时 asmdef。
|
||||
- 测试 asmdef 必须包含 `UNITY_INCLUDE_TESTS` 和 `TestAssemblies`,并且只引用被测系统真正需要的程序集。
|
||||
- 不把测试脚本混放在业务 `Editor/` 工具、运行时代码或功能目录中。
|
||||
- 测试生成资源使用唯一临时目录,并在 `TearDown` 中删除;生成物和 `.meta` 不得提交。
|
||||
|
||||
当前保留的测试职责如下:
|
||||
|
||||
| 套件 | 职责 |
|
||||
| --- | --- |
|
||||
| `Assets/Editor/Tests` | 音频/存档兼容、本地化、Yarn 元数据、设置值、关键 Prefab 契约 |
|
||||
| `FrameAnimationCoreTests` | 图模型、解析拒绝、播放会话、结束行为、请求句柄 |
|
||||
| `FrameAnimationImportTests` | Aseprite 解析和一次完整刷新链路 |
|
||||
| `FrameAnimationPlayerTests` | 运行时组件、请求替换、恢复和生命周期 |
|
||||
| `FrameAnimationTimelinePlayModeTests` | Timeline 接管、Seek 和释放播放器 |
|
||||
|
||||
## 5. 单个测试的设计
|
||||
|
||||
### 命名
|
||||
|
||||
使用 `Subject_State_Outcome`,例如:
|
||||
|
||||
```csharp
|
||||
[Test]
|
||||
public void PlayRequest_MissingPlayable_KeepsActiveRequest()
|
||||
```
|
||||
|
||||
名称描述行为,不描述内部调用顺序。
|
||||
|
||||
### 结构
|
||||
|
||||
每个测试保持清晰的 Arrange / Act / Assert 三段。一个测试可以有多个断言,但这些断言必须共同说明一个失败原因。
|
||||
|
||||
优先断言:
|
||||
|
||||
- 玩家或调用方可观察的最终状态。
|
||||
- 稳定的错误码、存档字段、资源地址或跨系统契约。
|
||||
- 必须保持的副作用边界,例如失败请求不能替换当前播放。
|
||||
|
||||
避免断言:
|
||||
|
||||
- 私有字段的每个中间值。
|
||||
- UI 层级中的全部节点和样式。
|
||||
- 与结果无关的精确调用次数或实现顺序。
|
||||
- 大段序列化快照;只检查兼容性所需字段。
|
||||
|
||||
### 用例合并
|
||||
|
||||
同一等价类的输入放在一个测试内循环验证,避免大量 `[TestCase]` 让测试面板产生虚假的覆盖感。只有不同输入应呈现为独立失败、且失败定位确实有价值时才使用 `[TestCase]`。
|
||||
|
||||
不要为了减少文件数,把互不相关的契约塞进同一个测试。合并的是重复路径,不是失败原因。
|
||||
|
||||
## 6. 稳定性与清理
|
||||
|
||||
- 不依赖网络、系统时间、线程调度顺序、随机种子或本机语言环境。
|
||||
- 修改全局状态时,必须在 `TearDown` 或 `finally` 中恢复。
|
||||
- 创建的 `GameObject`、`ScriptableObject`、纹理和 Sprite 必须销毁,销毁顺序与创建顺序相反。
|
||||
- 预期日志使用 `LogAssert.Expect`,不要为了通过测试而全局忽略日志。
|
||||
- 资产测试使用同步导入和唯一目录;清理逻辑应幂等,不能掩盖原始断言失败。
|
||||
- 不直接调用 `Resources.Load`;测试中的运行时资源获取同样遵守 `ResourceSystem` 规范。
|
||||
|
||||
## 7. 评审检查清单
|
||||
|
||||
新增或修改测试前检查:
|
||||
|
||||
1. 这是长期契约测试,还是阶段性的临时测试?目录是否匹配?
|
||||
2. 临时测试是否写明删除或转正条件?
|
||||
3. 现有长期测试能否扩展,而不是新建文件或新建程序集?
|
||||
4. 是否选择了能够覆盖该风险的最低层级?
|
||||
5. 上下层是否重复验证同一件事,而没有新增独立风险覆盖?
|
||||
6. 删除或重构实现后,长期测试是否仍然表达同一个需求?
|
||||
7. 失败信息能否直接告诉开发者哪条契约坏了?
|
||||
8. 是否创建了临时资产、全局状态或日志,且全部可靠清理?
|
||||
|
||||
若测试归属、层级选择或清理条件无法明确回答,不应立即添加测试。
|
||||
|
||||
## 8. Codex 行为约束
|
||||
|
||||
Codex 修改本仓库时遵守以下规则:
|
||||
|
||||
- 可以主动添加有明确反馈价值的 Unity Test,但不因“修改了代码”在每个层级机械补测试。
|
||||
- 对每个风险选择一个主要测试层级;只有存在不同的集成失败模式时才增加上层测试。
|
||||
- 未验收、中间态或实验功能的测试必须放在 `_Temporary`,并同时写明删除或转正条件。
|
||||
- 稳定契约优先扩展现有领域套件;没有新的长期领域边界时,不新建永久测试文件或 asmdef。
|
||||
- 添加 PlayMode 测试前,确认 EditMode 无法覆盖对应的 Unity 集成风险。
|
||||
- 如果测试数量或测试代码增幅明显大于生产改动,应先重新审视测试层级和重复覆盖。
|
||||
- 功能验收时检查 `_Temporary`,将最小必要契约测试转正并删除其余阶段性测试。
|
||||
- 功能被删除、契约被替换或测试只剩实现细节时,应同步删除测试,不保留“以防万一”的历史测试。
|
||||
|
||||
## 9. 运行方式
|
||||
|
||||
关闭正在打开该项目的 Unity Editor 后运行:
|
||||
|
||||
```powershell
|
||||
& 'C:\Program Files\Unity\Hub\Editor\2022.3.7f1c1\Editor\Unity.exe' `
|
||||
-batchmode -projectPath 'D:\UnityProject\aibis-dream' `
|
||||
-runTests -testPlatform EditMode `
|
||||
-testResults 'D:\UnityProject\aibis-dream\Temp\EditModeResults.xml' `
|
||||
-logFile 'D:\UnityProject\aibis-dream\Temp\EditMode.log'
|
||||
```
|
||||
|
||||
高风险运行时或 Timeline 修改再运行 PlayMode。纯文档、编辑器样式或不影响运行时契约的修改无需机械运行全部 PlayMode。
|
||||
Reference in New Issue
Block a user