Files
gplan-2/design/engineering/test-conventions.md
T
DKChan 9f94c9e100 工程(合回闸关联档+闸机编排快速失败): 收口跑批按改动关联面选测+红树快速失败——①tools/run_all_gates.sh 关联档选闸(推广 2026-10-28 影响面选闸):00 冒烟/01 系统测试/09 静态扫描三件保险恒跑不降(测量面批不再免 00);03/04 探针与 05 渲染闸维持影响面口径;02 单元与集成改「选套件跑」——套件集=需求卡片申报∪diff 推导并集(申报=本批 diff 触及卡片∪roadmap 进行中行登记卡片的对照表「验收测试」列测试文件名;推导=脚本内套件族映射表按 game/ 代码面文件归族,映射不到一律横切),横切(公共件前缀 config_db*/rng_service*/save_manager*/meta_progression*/main*、project.godot autoload 注册面、game/data/tables、PARITY 触面表)→ 02 回落全部套件;PARITY 触面表上移单点定义(gate 11 与横切判定共用同一清单);回执加 scope(full|related)与 scope_terms 字段,worktree-guard 校验逻辑不动——②game/tests/run_all.gd 快速失败:连续 3 个套件超时(可调常量 CONSECUTIVE_TIMEOUT_ABORT 初值 3)即中止整轮、汇总已见失败与超时清单、退出码非 0,不连续超时照旧逐个回收;_run_suite 改返 {fail, timed_out};新增 --only-source=gates 来源标记(关联档选跑横幅,区别于开发期选跑免责句),无选跑参数时行为与现状完全一致——③规则同批同步(禁只改闸不改规则):AGENTS.md「交付前验收」句与硬性红线改按关联档选测口径(所选域绿即可合回,全量回归由用户自发、合入后无自动兜底,2026-10-29 用户裁)+规范导航行同步;process-conventions §3 第 8 条合回闸细则同口径;test-conventions §4 改「提交前验证」、code-conventions §3 主题行随改;game/tests/README.md 工具速查 run_all.gd 行(快速失败+闸机关联档选跑+manifest 实数 65)与挂死防护注、门禁/本地闸回执字段句同步——④待办清单「合回闸关联档」「闸机编排快速失败」两条目完成整条删除。孤立验证:推导沙箱七场景(行为面归族/触面横切/测量面批/main* 横切/project.godot 横切/申报并集/横切优先)+run_all 闸源选跑 9/65 绿+2s 截止连续超时第 3 个中止(exit 1 带汇总)
2026-10-29 04:36:26 +08:00

155 lines
18 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`),按改动域选档、所选域全绿——该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。闸件增减只改脚本。代码域按**关联档选测**(2026-10-29 用户裁):02 单元与集成只跑「需求卡片申报 ∪ diff 推导」并集的套件,00 冒烟/01 系统测试/09 静态扫描三件保险恒跑不降,横切面改动(公共件/数据表/autoload/数值·存档·RNG·结算触面)回落全部套件;所选域全绿即可合回,全量回归由用户自发发起(合入后无自动兜底)。
行为探针使用随机化玩家行为(封印/停手/刷新/无效点击)——自动玩家不做的动作就是测试盲区。
## 5. 测试函数怎么写——验收清单条目的落码规范
需求卡片 §8「改动—验收对照表」每一条都要落成可执行的验证;本文定统一写法,卡里定每批的具体内容(测什么/注入哪里)。写卡期查不出执行载体的条目=不可测,当场呈报,禁拖到实现期。
1. **执行载体五选一,逐条声明**:单元测试(`tests/run_all`)/系统测试断言(`tests/system_test.gd`)/场景探针(`probe/` 下 `*_probe`)/MC 同种子逐项比对/人工核对(仅限无法自动化的纪律类需求点,如命名锁定、数值占位)。行为类条目以自动化载体为主。
2. **粒度与对应关系**:每条清单条目至少一个自动断言,且断言必须从该条目的真实入口进入;多条条目可合用一个测试函数,但撤销对应改动时每条都要能单独变红。断言失败消息末尾带编号尾缀〔卡短名 Vn〕(如〔秘宝批 V3〕)——断言与卡片清单可互相 grep 查找。**工具面局限备案(2026-10-27 迭代 39 验收审计)**:`Kit.check_quiet` 静默收集形态(计数不入判定即打印、失败消息进 problems 汇总)的断言消息不进 `tools/extract_checks.py` 扫描面——验收尾缀 grep 对该通道失效;如需覆盖属提取器改版(工程批)。
3. **断言词汇按验证对象五选一**:
| 词汇 | 适用场景 |
|---|---|
| 逐位相等 | 同种子下确定性公式与结算的精确逐项比对 |
| 区间/差值带 | 概率与平衡读数(MC 同种子配对比较,出带即红) |
| 快照比对 | 存档域结构、配置表行结构 |
| 重放 | 同种子断点回放、改前改后配对 |
| 状态/计数器断言 | 条件解锁、限次、充能——置数据→走路径→断计数值 |
4. **调试注入两层(能力测试与流程体验两分法的落码面)**:自动化测试=测试代码直调核心层写口构造状态(直改数据表行/计数器/存档域,不经 UI、不走正常资源积累流程——冒烟现役做法);人工体验=DebugOverlay/DS_DEBUG 调试面,服务用户目检(发布态该面恒隐、无 UI 开启口——写死行已随迭代 38 卡⑦ 删除,开发期临时启用=本地改 `_debug_entry` 缺省、不提交;能力测试不经 UI,本条口径不受影响)。清单条目的「怎么测」必须写到数据面粒度:改哪张表哪行、置哪个计数、调哪个核心写口。
5. **换代批旧验证约束同生死**:删除旧数据行/旧实现代码的批次,必须同批摘除只验证旧实现的断言与护栏校验,由新断言替代——旧件断言残留=冒烟假绿来源;合并后冒烟全绿且全仓无旧件残留引用为证。
6. **违约通道类需求点必须落独立负面套件(2026-10-28 迭代 40 立)**:预期运行期违约/致命路径的需求点(handler 通道、fail-fast 分支)一律落独立负面套件并按 manifest 登记 `expect=1, negative=true`,禁落现役非负面套件——headless 下 assert 失败必产不可捕获的 SCRIPT ERROR,非负面套件的输出被错误扫描命中即假红(先例=reward_dangling_treasure/counter_prefix_fatal/extended_row_violation/run_time_fatal 四件)。
## 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 四层与真实入口
- **单元**:不依赖场景树和真实宿主的纯逻辑;测试从领域类的对外方法进出,断言钉对外可观测面(返回值/信号/查询接口/落盘内容)。内部字段直查=受控例外:仅当该行为没有公开可观测面可钉时允许,对照表该行注明理由;存量不回头清扫,被批次触碰时顺带换代。
- **接口**:模块边界的公开契约——数据表加载、存档与断点、事件载荷、命令接口;测试从公开入口进出。接口级断言随单元套件落(`unit/contracts/`),不另设承载目录。
- **集成**:跨宿主的接线、持久、生命周期——例如 LevelRunner 把 level_counters 传给 BattleOrchestrator、两场战斗共享同一个桶、断点续玩;测试必须实例化真实宿主链,禁止用裸 EffectEngine 代替。
- **探针/golden**:玩家可达性与观感;真实实例化场景、驱动真实输入。
分层不是形式。改动发生在哪一层,验收就必须从那一层的真实入口进入。测试绕过了改动点,就不算这个改动的验收——这正是「断言存在、接线坏了却不红」的来源。
### 8.3 改动—验收对照表
需求卡片列需求侧对照表;实现报告列实际对照表。每行至少六格:
| 需求点 | 改动(文件:行/行为) | 验收测试(名称+文件:行+尾缀) | 层级 | 真实入口 | 判据绑定面 |
|---|---|---|---|---|---|
实现报告另加「结果」格(绿/红/未过及依据)。两端都要对上:
- **正向**:每个需求点至少一行;每行必须有真实入口和判据绑定面。缺任一项=该需求点未验收。
- **反向**:diff 里每个行为改动至少一行;没有对应行的改动,必须写明「非行为改动」或「由第 X 行覆盖」。
- **测试侧**:新增或改写的每条断言至少挂一行;挂不上的孤儿断言删掉或补挂到需求点。
**能力清单维护锚(2026-10-29 用户裁改版定稿)**:`game/tests/README.md`「能力清单」是**实现指导+阅读导航+义务声明**——只宣布「要验证什么」,不记「是否已验证/是否跑过」;落地账归现行验收闭合链(改动—验收对照表逐条挂断言+合回前独立验收审计),本清单不承载验证状态。表为四列:能力|系统|真实入口|代表用例(`文件:函数`)。**维护归写卡段,当场入表**:写需求卡片时,本批新增能力当场写入能力行——真实入口取自设计文档已定的入口,代表用例=卡里定死的计划测试(`文件:函数`);受影响存量行(入口/用例名变更)同批更新;能力退役删行。**实现照单落名**:测试函数名以清单为唯一依据,名实不符=卡片没写清=停卡退回。**收尾核对**:批末收尾只核对表上名字与实际落地一致(改名则回表),不刷任何批次字段;实现批禁写该文件的现行红线不变(process-conventions §3 第 11 条),核对义务归收尾提交。三层模型见 §8.9。改动—验收对照表纪律不变:新增或改写的每条断言仍须挂一行(上文「测试侧」);断言改判仍受 §8.4 真实入口口径约束。
### 8.4 真实入口断言(验收闭合的主判据)
- **唯一口径(2026-10-24/2026-10-28 用户两裁)**:每个需求点与每个行为改动的验收=一条从真实入口进入的断言,可 grep 定位、在当轮真实跑批中执行并通过。**禁变异类测试**(把生产码或数据表改成错值看断言抓不抓得到);**禁撤销类取证**(回退本批改动或该 hunk 看断言红不红)——两者都是人为制造红,属伪造证据,一律禁止。
- **判据绑可观测提交面**:断言判据要绑「被测改动特有的可观测提交面」(信号发射、事件、入参、绘制记录、状态源取值),不绑多层防御之后的终态——否则改动被下游兜底吸收时终态不变、断言假绿。例:探针「雾不扩可点域」首版只看屏/选路态终态,命中面放宽后断言仍绿;改绑 `route_chosen` 发射面后即红。
- **判据绑不上的出口=停卡呈报**:某改动确实造不出可观测提交面(纯防御分支、release 独有位)时,走静态判据+代码评审注记并在对照表申报观测面局限;不得自造替代证明。
- **正面效力自检**:精确等式优于不等式与非空即过;扫描类断言带「扫描面 > 0」非空前置,防扫描面为空静默绿。
- 持久化、生命周期、存档、跨战共享这类接线逐条配断言;判定可靠性细则见 §7。
### 8.5 禁止削弱测试
实现期不得为了让测试变绿而删除断言、放宽阈值、缩小扫描面、把真实入口换成下层直调。确需改判据=需求卡片没写清=上游缺陷:停该卡、呈报退回写卡段,不在实现期自行决定。卡片可预先声明允许改动的旧断言及理由;未声明的削弱由独立审计判红。
### 8.6 测试时机
不要求 TDD,不要求先红后绿。允许实现后补测试;但补的测试必须满足 §8.4 的真实入口断言口径。实现完再补、补完就绿,不算验收。
### 8.7 独立验收审计
合并前由没有参与实现的会话或子代理做一次审计。审计只读需求卡片、实际对照表、diff、测试文件和实现报告,自己跑最窄测试抽查,不采信报告里的「已全绿」。审计至少做四件事:
1. 逐行核对对照表:测试存在、真的执行、尾缀能 grep 到、真实入口没有绕过改动点;
2. 抽查判据绑定面:断言从真实入口进入、钉在被测改动的特有可观测提交面上,且当轮真实跑批执行过;
3. 检查测试削弱:删除断言、放宽阈值、缩小扫描面、换下层直调,逐处要卡片申报或用户批准。
4. 核对内部字段直查的受控例外:直查行是否已在对照表注明理由(该行为无公开可观测面可钉)。
审计报告落 `reports/iter<N>-验收审计-<日期>.md`,逐行写「通过/缺漏/例外及依据」。未通过不得合并。测试执行两入口铁规不变:审计在迭代 loop 内启动的子代理算 loop;另开会话须用户点名。
### 8.8 生效与旧卡
本规自 2026-10-21 起对文件名日期不早于该日的需求卡片生效;旧卡不回头改。旧卡在开工前缺对照表的,由写卡段补,补完再开工。
### 8.9 三层模型(验收意图·义务清单·执行证据,2026-10-29 定稿)
验收体系分三层,各层各管一件事,互不代行:
| 层 | 载体 | 切分轴 | 一行是什么 |
|---|---|---|---|
| 验收意图 | 系统文档「验收标准」节 | 按 owner 文档 | 一条玩家或系统可观测的判据 |
| 义务清单 | `game/tests/README.md`「能力清单」 | 按玩家旅程与系统归属 | 一条待验证义务:能力+真实入口+代表用例(`文件:函数`) |
| 执行证据 | 测试套件断言 | 按代码归属 | 一条从真实入口进入的断言 |
四条法则:
1. **每格非空**:每个系统文档有验收标准节;每个能力行有真实入口与至少一个代表用例;每条断言挂在某个需求点或能力行上。
2. **每条断言有归属**:孤儿断言删掉或补挂;不建无归属断言。
3. **数量不守恒、承接守恒**:三层条数天然不等——一条验收意图可由多个能力承接,一条能力义务由多条断言覆盖。要守的是两个满射:每条验收意图至少有一条能力义务承接,每条能力义务至少有一条真实入口断言落地。数量对账只在批内做,见第 4 条。
4. **无全局对账总账**:不建逐断言账本,不做跨层条数对账;对账只在每批的改动—验收对照表里做(§8.3)。历史教训:阶段 2 退役的逐断言账本正因把义务清单层与证据层压成一本账而失修。
**维护时序(2026-10-29 改版:废「滞后一个迭代」)**:能力清单归写卡段维护——写需求卡片时当场入表:本批新增能力当场增行(真实入口取自设计文档已定的入口,代表用例=卡内定死的计划测试 `文件:函数`),受影响存量行(入口/用例名变更)同批更新,能力退役删行;实现批禁写该文件(process-conventions §3 第 11 条)。实现照单落名:测试函数名以清单为唯一依据,名实不符=卡片没写清=停卡退回。批末收尾只核对表上名字与实际落地一致(改名则回表),不刷任何批次字段——清单不记「是否已验证/是否跑过」,落地账在改动—验收对照表(§8.3)与独立验收审计(§8.7)。
**文档纯度**:验收意图与能力描述只写玩家或系统可观测面,禁写代码路径(`.gd`/`.py`/`.sh`)、测试文件名、断言名;唯一例外=配置文件索引(`game/data/tables/*.tres` 这类表路径与表名)。规则正文见 doc-conventions §5「禁实现内容」。
**机器闸**:本节的结构由文档域闸「能力审计」机检——GDD 纯度(同款禁实现内容口径)、系统文档验收标准节非空、能力行四列与非空、代表用例(`文件:函数`)与真实入口可核(双通道:代码在或卡在——计划中的用例与入口由需求卡片锚定)、覆盖域表与能力行对得上;红项按新增红处理。闸件清单与域划分的唯一出处=`tools/run_all_gates.sh` 头部。