Files
gplan-2/design/engineering/test-conventions.md
T

130 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 测试规范(Dungeon Sweeper)
> 约束范围:`game/probe/ 与 game/sim/`、`game/tests/` 与一切场景探针的编写与验收。
> 分工:测试**执行许可**(什么时候允许跑测试)归 AGENTS.md「硬性红线·测试执行两入口铁规」;**跑什么闸、什么顺序**归 `tools/run_all_gates.sh`(脚本即清单,本文与 AGENTS.md 均不另列);本文管测试怎么写、怎么算合格。每件测试工具**是什么、判什么、怎么单独跑、什么时候适合单独用**——见 `game/tests/README.md`(测试工具手册),本文不重复工具介绍。
> 归属:随游戏逻辑写/改的测试=需求改动的一部分(随需求卡片同批落地);不伴随逻辑改动的纯测试维护=工程改动(不写卡,用户发令直改)。
**定测试方案前必须通读本文。**
---
## 1. 测试总纲——"存在"必须证明"可达且正确"
核心层验收标准通过不算有测试:GDScript 编译不查字典字段与时序,呈现层错误只在运行时暴露。
测试必须证明功能可达(用户真的能用到)且行为正确(结果符合验收标准)。
## 2. 场景级探针标配
每新增一个可交互 UI 区域(视图/覆盖层/面板),必须配套场景级 headless 探针:
真实实例化场景 → 驱动真实输入路径 → 断言可见性与状态。
凡模态覆盖层,探针必须断言:打开后自身 visible 且遮挡物已隐藏(模态被遮挡 = 功能存在但不可达)。
## 3. 缺陷修复必须沉淀回归断言
每个缺陷修复必须在探针或冒烟中留下能抓住该缺陷的断言(例:封印动作耗尽 AP → 必须进入第 2 轮;
选用道具 → 库存态必须变 Used)。没有回归断言的修复视为未完成。
## 4. 提交前全量验证
交付前跑 `tools/run_all_gates.sh`(不带 `--quick`)全绿——该脚本是全部验证闸的唯一入口与唯一清单,本文禁另列闸件清单。闸件增减只改脚本。
行为探针使用随机化玩家行为(封印/停手/刷新/无效点击)——自动玩家不做的动作就是测试盲区。
## 5. 测试函数怎么写——验收清单条目的落码规范
需求卡片 §8「改动—验收对照表」每一条都要落成可执行的验证;本文定统一写法,卡里定每批的具体内容(测什么/注入哪里)。写卡期查不出执行载体的条目=不可测,当场呈报,禁拖到实现期。
1. **执行载体五选一,逐条声明**:单元测试(`tests/run_all`)/系统测试断言(`tests/system_test.gd`)/场景探针(`probe/` 下 `*_probe`)/MC 同种子逐项比对/人工核对(仅限无法自动化的纪律类需求点,如命名锁定、数值占位)。行为类条目以自动化载体为主。
2. **粒度与对应关系**:每条清单条目至少一个自动断言,且断言必须从该条目的真实入口进入;多条条目可合用一个测试函数,但撤销对应改动时每条都要能单独变红。断言失败消息末尾带编号尾缀〔卡短名 Vn〕(如〔秘宝批 V3〕)——断言与卡片清单可互相 grep 查找。
3. **断言词汇按验证对象五选一**:
| 词汇 | 适用场景 |
|---|---|
| 逐位相等 | 同种子下确定性公式与结算的精确逐项比对 |
| 区间/差值带 | 概率与平衡读数(MC 同种子配对比较,出带即红) |
| 快照比对 | 存档域结构、配置表行结构 |
| 重放 | 同种子断点回放、改前改后配对 |
| 状态/计数器断言 | 条件解锁、限次、充能——置数据→走路径→断计数值 |
4. **调试注入两层(能力测试与流程体验两分法的落码面)**:自动化测试=测试代码直调核心层写口构造状态(直改数据表行/计数器/存档域,不经 UI、不走正常资源积累流程——冒烟现役做法);人工体验=DebugOverlay/DS_DEBUG 调试面,服务用户目检。清单条目的「怎么测」必须写到数据面粒度:改哪张表哪行、置哪个计数、调哪个核心写口。
5. **换代批旧验证约束同生死**:删除旧数据行/旧实现代码的批次,必须同批摘除只验证旧实现的断言与护栏校验,由新断言替代——旧件断言残留=冒烟假绿来源;合并后冒烟全绿且全仓无旧件残留引用为证。
## 6. 判定三硬闸——闸承载的构造义务(迭代 29 起)
任何**新增或改写的闸承载**(闸脚本/批跑编排/探针入口)必须三闸齐备;现行各闸状态登记归架构文档「测试与质量域」节(本文不另列闸件清单):
1. **退出码为唯一权威判据**——输出里的 PASS 串只作附加判据不作判据主体;打印 PASS 后 `quit(1)` 必须判红。
2. **SCRIPT ERROR / Parse Error 扫描面在位**——GDScript 脚本错误不改进程退出码、不计失败数(机制事实见 §7),错误标记必须扫输出,且判据必须覆盖「跑了但半途脚本错误」态——退出码 0 但输出缺预期签名同样判红(防静默退出)。
3. **断言数下限**——零断言的正常返回与断言失败必须区分开(现行做法:`Kit.finish` 零断言返回专用退出码 2,不与「断言失败=1」混用,否则致命类套件的期望码协议会放过空跑)。
- 负面套件(测试目标就是产生错误输出)仅可豁免第 2 闸,且须在 manifest 显式标记,其余两闸照判。
- 子进程输出须经系统命令解释器重定向落盘再读——`OS.execute` 的输出管道在本机恒空。
**新写测试件全类型标注**——新写的测试件(冒烟断言/探针/单测)参数、返回值、容器一律带类型标注,与 S9 对产品码的要求同型;存量不强制清扫,被批次触碰时顺带换代。暂不机器闸(S9 扫描面=产品码,probe/sim/tests 不在扫描面是测量面直构惯例的既有裁量)。
## 7. 判定可靠性优先于覆盖率(迭代 29 核心命题)
- 「跑一遍没崩=通过」无效:测试绿只证明**当前代码没犯这个错**,不证明**犯了这个错会被抓住**;断言存在不等于断言执行。
- **脚本错误会静默中断所在函数的剩余断言,而套件仍报全绿**(机制事实):GDScript 无脚本错误陷阱 API,错误不改进程退出码、不计失败数。三处实证:①2026-09-19「309 项 0 失败」实为断言未跑全(状态记录 83);②敌人表行集纪律 10 条断言因定型数组赋值错误从未执行(迭代 29 修复后两条正向见红=夹具本缺锚行);③`Callable` 形参类型写错致解析函数返回 null、后续断言整段蒸发。
- 由此产生两条义务:**写闸件**按 §6 配三硬闸;**写验收函数**必须自证断言全数执行——撤销证明(§8.4)为主证,零命中自检(扫描面为空即判红)为辅证。
- **覆盖率不是目标**:行覆盖率度量缺失(Godot 无内建,第三方属新基建,是否引入待办清单在册待裁);现行补洞=零指名单测清单+人工判定,判定可靠性靠上列自证手段,不靠覆盖率数字。
## 8. 改动级验收闭合(2026-10-21 立)
### 8.1 验收单位是行为改动
一个行为改动 = 新增或修改一条函数、一条数据、一处接线、一个生命周期行为、一条玩家可见文案。纯改名、注释、格式化不算行为改动,但必须在对照表里单独列一行写「非行为改动」及判断依据。禁止用「重构」两个字把行为改动藏起来。
### 8.2 四层与真实入口
- **单元**:不依赖场景树和真实宿主的纯逻辑;测试直接调用被测函数。
- **接口**:模块边界的公开契约——数据表加载、存档与断点、事件载荷、命令接口;测试从公开入口进出。
- **集成**:跨宿主的接线、持久、生命周期——例如 LevelRunner 把 level_counters 传给 BattleOrchestrator、两场战斗共享同一个桶、断点续玩;测试必须实例化真实宿主链,禁止用裸 EffectEngine 代替。
- **探针/golden**:玩家可达性与观感;真实实例化场景、驱动真实输入。
分层不是形式。改动发生在哪一层,验收就必须从那一层的真实入口进入。测试绕过了改动点,就不算这个改动的验收——这正是「断言存在、接线坏了却不红」的来源。
### 8.3 改动—验收对照表
需求卡片列需求侧对照表;实现报告列实际对照表。每行至少六格:
| 需求点 | 改动(文件:行/行为) | 验收测试(名称+文件:行+尾缀) | 层级 | 真实入口 | 撤销证明 |
|---|---|---|---|---|---|
实现报告另加「结果」格(绿/红/未过及依据)。两端都要对上:
- **正向**:每个需求点至少一行;每行必须有真实入口和证明方式。缺任一项=该需求点未验收。
- **反向**:diff 里每个行为改动至少一行;没有对应行的改动,必须写明「非行为改动」或「由第 X 行覆盖」。
- **测试侧**:新增或改写的每条断言至少挂一行;挂不上的孤儿断言删掉或补挂到需求点。
**验证集清单维护锚(2026-10-22 用户裁)**:`game/tests/验证集清单.md` 是迁移断言全体的冻结参照清单(机器提取生成、只剪切禁改写、出口逐条核对,机制见该文件头注;清单自断言迁移批第 0 步建立时起存在)——清单在册后,任何断言**新增、删除或改判**必须同步该清单,与改动—验收对照表同一条纪律:清单没跟上的断言改动视同未完成。
### 8.4 撤销证明
- **修改既有行为**:只撤销该改动(回退该 hunk 或该处接线)→ 跑能覆盖它的最窄测试 → 对应断言必须变红 → 还原 → 复绿。
- **新增行为**:只撤销该新增(回退该 hunk 或删去该新增段)→ 跑能覆盖它的最窄测试 → 对应断言必须变红 → 还原 → 复绿。
- **撤销后仍然绿**:说明断言没钉在这个改动上,判缺漏;不得用「代码能跑通」代替。
- **判据绑可观测提交面**:端到端断言的判据要绑「被测改动特有的可观测提交面」(信号发射、事件、入参、绘制记录),不绑多层防御之后的终态——否则改动被下游兜底吸收时终态不变、断言假绿。例:探针「雾不扩可点域」首版只看屏/选路态终态,命中面放宽后断言仍绿;改绑 `route_chosen` 发射面后即红。
- 每张任务卡至少一条撤销证明记录;持久化、生命周期、存档、跨战共享这类接线逐行做。
### 8.5 禁止削弱测试
实现期不得为了让测试变绿而删除断言、放宽阈值、缩小扫描面、把真实入口换成下层直调。确需改判据=需求卡片没写清=上游缺陷:停该卡、呈报退回写卡段,不在实现期自行决定。卡片可预先声明允许改动的旧断言及理由;未声明的削弱由独立审计判红。
### 8.6 测试时机
不要求 TDD,不要求先红后绿。允许实现后补测试;但补的测试必须通过 §8.4 的撤销证明。实现完再补、补完就绿,不算验收。
### 8.7 独立验收审计
合并前由没有参与实现的会话或子代理做一次审计。审计只读需求卡片、实际对照表、diff、测试文件和实现报告,自己跑最窄测试抽查,不采信报告里的「已全绿」。审计至少做三件事:
1. 逐行核对对照表:测试存在、真的执行、尾缀能 grep 到、真实入口没有绕过改动点;
2. 抽查撤销证明:每张任务卡至少一条,按记录复现红/绿;
3. 检查测试削弱:删除断言、放宽阈值、缩小扫描面、换下层直调,逐处要卡片申报或用户批准。
审计报告落 `reports/iter<N>-验收审计-<日期>.md`,逐行写「通过/缺漏/例外及依据」。未通过不得合并。测试执行两入口铁规不变:审计在迭代 loop 内启动的子代理算 loop;另开会话须用户点名。
### 8.8 生效与旧卡
本规自 2026-10-21 起对文件名日期不早于该日的需求卡片生效;旧卡不回头改。旧卡在开工前缺对照表的,由写卡段补,补完再开工。