CCGS 非确定性测试检测指南:用 /test-flakiness 技能定位抖动测试并守护测试套件稳定性
【免费下载链接】Claude-Code-Game-StudiosTurn Claude Code into a full game dev studio — 49 AI agents, 72 workflow skills, and a complete coordination system mirroring real studio hierarchy.项目地址: https://gitcode.com/GitHub_Trending/cl/Claude-Code-Game-Studios
Claude Code Game Studios(CCGS)在其 Skill Testing Framework 的质量保障层中定义了/test-flakiness技能,用于检测测试套件中的非确定性(non-deterministic)测试。本文以 test-flakiness.md 测试规范为主体,完整展开该技能的分析模式、判定等级、五种典型抖动模式、五个可执行的测试用例,并结合仓库中 qa-lead 角色、analysis 类目质量指标以及 test-setup / test-helpers / regression-suite 等相邻技能,说明其在真实游戏研发流程中的位置与用法。读完本文,你将掌握抖动测试的完整检测协议:如何基于测试历史日志计算通过率、如何在无历史时退回源码模式扫描、如何区分 SUSPECT 与 CONFIRMED 判定,以及如何把检测结果沉淀为可供 QA Lead 决策的报告。
技能定位:面向 QA Lead 的只读分析技能
/test-flakiness是 CCGS 技能体系中analysis类目下的一个成员(同属该类的还有 consistency-check、code-review、security-audit 等)。根据规范,它的核心职责是:
通过分析测试历史日志(如果可用)或扫描测试源代码中常见的抖动模式(无种子随机数、实时等待、外部 I/O),检测非确定性测试。
其行为约束非常明确:
- 不触发任何导演门禁(Director Gate):抖动检测是面向 QA Lead 的顾问型质量技能,不调用任何导演代理;
- 不主动写入文件:技能本身是只读的,只有用户明确同意后,才可能产出可选报告(需先询问 "May I write");
- 三种判定结论:
NO FLAKINESS(无抖动)、SUSPECT TESTS FOUND(疑似抖动)、CONFIRMED FLAKY(确认抖动)。
这一"分析→提示→人工决策"的定位与 quality-rubric.md 中 analysis 类目的质量指标完全吻合:AN1 — 只读扫描(分析阶段只用 Read/Glob/Grep,不写不编辑)、AN2 — 结构化发现表(输出必须包含发现表/清单,而非纯散文)、AN3 — 禁止自动写(任何建议的写入都要以 "May I write" 把关)、AN4 — 分析期不触发导演门禁(产生发现供人工评审)。这正是理解该技能一切行为的前提。
双模式分析机制:历史优先,源码兜底
规范的第一条 Protocol Compliance 明确规定了分析路径的优先级:
读取测试历史日志(可用时);不可用时退回源码分析模式一:历史日志分析(History-based Analysis)
当production/qa/test-history/目录存在且包含多次运行的日志时,技能按以下步骤工作:
- 读取
production/qa/test-history/下的测试运行日志; - 对每个测试计算其在所有可用运行中的通过率(pass rate);
- 以95% 通过率为阈值进行 SUSPECT 分类:低于阈值的测试按名称标记;
- 记录失败模式(如错误信息不一致、有时超时有时数值错误),并给出通过率(分数与百分比);
- 给出建议:调查该测试的时序依赖或状态依赖。
规范特别强调(见 Coverage Notes):95% 阈值是实现细节,测试验证的是"间歇性失败会被标记"这一行为,而不是具体的阈值数值。这意味着实现方可以根据项目实际运行量调整阈值,而不破坏协议。
模式二:源码模式扫描(Source-only Analysis)
当production/qa/test-history/不存在时,技能明确提示:
"No test history available — analyzing source code for flakiness patterns only"
然后扫描所有测试文件中的已知抖动模式(详见下一节),对命中项标记为FLAKINESS RISK(源码模式层面的风险,而非历史确认)。规范要求必须清晰标注当前使用的分析模式(历史模式 vs 纯源码模式),因为这会直接影响判定的置信度——源码模式只能得到 SUSPECT,永远不能得到 CONFIRMED。
五大抖动模式:源码扫描的判定依据
综合 test-flakiness.md 的测试用例与相邻的 test-evidence-review.md 规范,技能源码扫描覆盖以下典型模式(前两类直接出现在本技能用例中):
| 模式 | 检测特征 | 典型代码(GDScript 示例) | 风险等级 |
|---|---|---|---|
| 无种子随机数 | randf()/randi()等调用之前没有seed()调用 | var roll = randf() # unseeded random — non-deterministic | FLAKINESS RISK |
| 实时时钟断言 | 用实时时钟做断言基准,如OS.get_ticks_msec() | assert_lt(OS.get_ticks_msec() - start, 100) | FLAKINESS RISK |
| 实时等待 | 基于真实时间的等待而非信号/mock,如create_timer(1.0) | await get_tree().create_timer(1.0).timeout | FAIL(违反确定性标准) |
| 直接外部 I/O | 测试直连真实 API / 文件系统,如 HTTPRequest 直呼线上 URL | HTTPRequest.new().request("https://api.example.com/auth") | FAIL(违反隔离标准) |
| 环境性失败 | 缺失资源、平台不符导致的失败(非测试自身非确定性) | —(由技能区分处理) | 不计入抖动 |
其中"无种子随机数"是 Case 3 的核心场景:loot_drop_test.gd中randf()前没有seed()调用,导致每次运行产生的随机序列不同,断言assert_gt(roll, 0.5)的结果随运行而波动。技能的修复建议是:在随机调用前设置种子(seeding),或对随机函数进行 mock(mocking)。
需要注意规范在 Coverage Notes 中划出的边界:因环境问题(缺失资源、平台不符)导致的测试失败不属于抖动。技能需要区分"环境失败"与"测试自身非确定性"两种情形,前者是环境配置问题,后者才是本技能的扫描对象。
三种判定等级的语义与证据要求
规范的判定体系是分层的,证据强度不同,结论置信度不同:
| 判定 | 触发条件 | 证据要求 | 动作 |
|---|---|---|---|
| NO FLAKINESS | 所有测试在全部运行中一致通过,或源码扫描无命中 | 历史数据或源码扫描 | 不写任何文件 |
| SUSPECT TESTS FOUND | 存在低于通过率阈值的测试(如 7/10、70%),或源码中发现抖动模式 | 历史日志中的间歇失败或源码模式 | 按名称标记、展示通过率、记录失败模式、建议调查 |
| CONFIRMED FLAKY | 历史日志显示反复失败(如 10 次运行失败 6 次) | 仅限历史证据,源码模式不足以确认 | 呈现发现,可选项提供书面报告(需 "May I write") |
关键语义区别(Case 3 断言明确要求):源码模式命中只能给出 SUSPECT TESTS FOUND,绝不能升级为 CONFIRMED FLAKY——因为缺乏历史运行数据来"确认"抖动。CONFIRMED 判定是历史证据的专属权力。
五个测试用例:技能行为的完整验证矩阵
规范通过 5 个带 fixture 的测试用例,把上述行为固化为可自动验证的断言。这些用例同时也是理解技能内部执行流程的最佳入口。
Case 1:快乐路径 — 历史干净,无抖动
Fixture:production/qa/test-history/包含 10 次运行的日志,所有测试 10 次全部通过(每测试 100% 通过率),无失败模式。
预期行为:
- 技能读取测试历史日志;
- 计算每个测试跨 10 次运行的通过率;
- 全部通过、无不一致 → 判定
NO FLAKINESS; - 不写入任何文件。
断言要点:历史可用时读取历史;按测试计算跨运行通过率;全部一致通过时判定无抖动;零文件写入。
Case 2:疑似抖动 — 历史中存在间歇性失败
Fixture:10 次运行日志中test_combat_damage_applies_crit_multiplier通过 7 次、失败 3 次,且失败信息不一致(有时超时、有时数值错误)。
预期行为:
- 计算通过率为70%(7/10),低于 95% 阈值;
- 按名称标记为 SUSPECT,展示通过率(分数与百分比)与失败模式;
- 判定
SUSPECT TESTS FOUND; - 建议调查该测试的时序依赖或状态依赖。
断言要点:低于阈值的测试按名称标记;每个疑似测试展示通过率分数与百分比;可检测到时记录失败模式;给出调查建议。注意这里的"失败信息不一致"(一会儿 timeout、一会儿 wrong value)本身就是非确定性的强信号——若每次失败原因相同,反而更像确定性缺陷。
Case 3:源码模式 — 无种子随机数
Fixture:无历史日志;tests/unit/loot/loot_drop_test.gd包含:
var roll = randf() # unseeded random — non-deterministic assert_gt(roll, 0.5, "Loot should drop above 50%")预期行为:
- 找不到历史日志;
- 退回源码分析;
- 检测到
randf()调用前无seed()调用; - 标记为 FLAKINESS RISK(源码模式,非历史确认);
- 判定
SUSPECT TESTS FOUND(模式检测到但无历史确认); - 建议在调用前播种或 mock 随机函数。
断言要点:无历史时启用源码分析兜底;未播种随机数被识别为抖动风险;判定严格停留在 SUSPECT(无历史可确认);修复建议明确指向 seeding 或 mocking。
Case 4:无历史 — 纯源码分析覆盖常见模式
Fixture:production/qa/test-history/不存在;tests/下 15 个测试文件;扫描发现 2 个测试用OS.get_ticks_msec()做时序断言;无其他模式命中。
预期行为:
- 检查历史——未找到;
- 明确提示"无历史可用,仅做源码模式扫描";
- 扫描已知模式:无种子随机、实时等待、系统时钟使用;
- 2 个使用
OS.get_ticks_msec()的测试被标记为 FLAKINESS RISK; - 判定
SUSPECT TESTS FOUND。
断言要点:明确声明当前处于纯源码分析模式;覆盖三类常见模式扫描(随机、时间类断言、外部 I/O);OS.get_ticks_msec()用于断言被标记为抖动风险;源码模式命中时给出 SUSPECT 判定。这个用例直接验证了技能在从未配置过测试历史的早期项目中也能正常工作。
Case 5:门禁合规 — 无门禁,报告仅作建议
Fixture:测试历史显示 1 个 CONFIRMED FLAKY 测试(10 次运行失败 6 次);review-mode.txt内容为full。
预期行为:
- 分析历史,识别 1 个已确认抖动的测试;
- 无论 review mode 是什么,都不触发任何导演门禁;
- 判定
CONFIRMED FLAKY; - 呈现发现,提供可选的书面报告;
- 若用户选择写入:"May I write to
production/qa/flakiness-report-[date].md?"
断言要点:任何评审模式下都不调用导演门禁;CONFIRMED FLAKY 判定必须基于历史证据(仅源码模式不够);可选报告写入前必须征得 "May I write";报告对 qa-lead 仅作建议,技能不会自动禁用任何测试。
与其他 QA 技能的协同工作流
/test-flakiness不是孤立运行的。在 CCGS 的 QA 工作流中,它与多个相邻技能构成完整闭环:
/test-setup(搭建测试框架,无框架时的前置步骤) → /test-helpers(生成确定性工厂函数与 mock 桩) → 测试编写与运行(CI 产生测试历史) → /test-flakiness(本文技能:检测抖动) → /test-evidence-review(评审测试命名/确定性/隔离性等质量标准) → /regression-suite(将 AC 映射到测试断言,产出覆盖报告) → /gate-check(触发 QL-TEST-COVERAGE 等导演门禁,单独技能)关键协同点:
- 上游依赖:test-setup 按引擎搭建
tests/unit/、tests/integration/、tests/performance/、tests/playtest/四层结构(Godot 用 GdUnit4、Unity 用 asmdef、Unreal 用 headless runner)。若发现production/qa/test-history/从未产生过日志,说明 CI 测试运行尚未建立,可回溯检查 test-setup 与 CI 配置。 - 确定性共建:test-helpers 生成的工厂函数要求"使用依赖注入(无单例)",mock 桩则用于隔离外部依赖——这正是从源头消灭抖动(随机数、实时时钟、外部 I/O)的工程手段,与本技能扫描的模式一一对应。
- 兄弟技能:test-evidence-review 从另一角度评审测试质量(命名规范、Arrange/Act/Assert 结构、确定性、隔离性、无硬编码魔数),其 Case 2/3 中把
create_timer(1.0)实时等待和直连外部 API 判为 FAIL 级发现,与本技能的扫描模式高度重合;两技能共享同一套对"什么是好测试"的定义。 - 覆盖维度补充:regression-suite 回答"AC 有没有测试"(覆盖),本技能回答"测试稳不稳定"(确定性),二者互补,且都归属
production/qa/目录产出报告。 - 下游交接:发现确认的抖动测试后,报告供 qa-lead 决策。qa-lead 的 Case 4 展示了相关冲突处理范式:当 gameplay-programmer 与 qa-lead 就"定时断言是否够确定性"产生分歧时,qa-lead 承认技术性抖动关切并升级到 lead-programmer 做技术裁决,而非单方面覆盖——这与本技能"仅报告、不自动禁用测试"的建议性质一脉相承。
- 完整质量门禁:本技能与 test-evidence-review 均不触发导演门禁;而覆盖率的正式把关由
/gate-check触发的 QL-TEST-COVERAGE 门禁(qa-lead 的另一个 Gate ID)负责,属于独立技能调用,见 test-evidence-review.md 的 Case 5。
协议合规清单与验证方法
技能自身的合规要求
规范结尾的 Protocol Compliance 是对/test-flakiness行为的最终验收清单:
- 历史日志可用时读取历史;不可用时退回源码分析;
- 清晰标注当前使用的分析模式(历史 vs 纯源码);
- 使用抖动阈值(如 95% 通过率)进行 SUSPECT 分类;
- CONFIRMED FLAKY 必须基于历史证据;SUSPECT 可覆盖纯源码模式;
- 不禁用、不修改任何测试文件;
- 不触发导演门禁;
- 判定严格限定为三选一:
NO FLAKINESS/SUSPECT TESTS FOUND/CONFIRMED FLAKY。
如何用 /skill-test 验证本技能
本仓库的测试框架支持用/skill-test对技能进行自动化验证(见 skill-test.md):
- 静态检查:
/skill-test static test-flakiness验证 5 项结构断言——frontmatter 必需字段(name、description、argument-hint、user-invocable、allowed-tools)、≥2 个 phase 标题、包含三个判定关键词、不含强制 "May I write" 语言(只读技能,可选报告才需批准)、存在下一步交接(next-step handoff)。 - 规范评估:
/skill-test spec test-flakiness逐条评估上述 5 个测试用例的断言,产生按用例的 PASS/FAIL 表,最终判定 PASS(全部通过)/ PARTIAL(部分)/ FAIL(多数失败)。
覆盖说明与已知边界
规范 Coverage Notes 明示了两点重要边界,引用时需注意:
- 阈值是实现细节:95% 是建议值,测试验证的是"间歇失败会被标记",而非具体阈值数字——实现方可按项目调整。
- 环境失败 ≠ 抖动:缺失资源、平台不符导致的失败应被区分对待,不被计为非确定性;这一区分在测试用例层面未被显式覆盖。
- 依据 CLAUDE.md 的说明,所有 spec 描述的是当前行为而非理想行为,可能编码了已知缺陷;当技能在实践中表现异常时,应先修正技能,再更新 spec——spec 失败应视为"需要调查"而非"技能必然有错"。
实践落地建议
要在真实游戏项目中把/test-flakiness用起来,推荐按如下顺序落地:
- 先搭骨架:运行
/test-setup建立引擎对应的测试目录与 runner(Godot 的godot --headless --script tests/gdunit4_runner.gd等 CI 命令见 test-setup.md),确保测试可重复运行并能沉淀历史日志到production/qa/test-history/。 - 从源头防抖:编写测试时优先使用
/test-helpers生成带依赖注入的工厂函数与 mock 桩,避免测试直连外部 API、依赖实时时钟或未播种的随机数。 - 常态化检测:定期调用
/test-flakiness。早期无历史日志时它会自动进入源码扫描模式,仍能抓出未播种随机数、OS.get_ticks_msec()时序断言等隐患;积累运行日志后升级为通过率统计,识别间歇失败。 - 分级处置:SUSPECT 级发现组织排查(优先怀疑时序与共享状态依赖);CONFIRMED FLAKY 级写入
production/qa/flakiness-report-[date].md报告(先征得 "May I write"),交由 qa-lead 决定修复优先级与是否纳入发布质量门槛——绝不自动禁用测试。 - 双技能交叉验证:对同一批测试配合
/test-evidence-review做命名、结构、确定性、隔离性评审,用/regression-suite确认 AC 覆盖无缺口,三者共同支撑发布前的质量证据链。
结语
/test-flakiness是 CCGS 质量保障层中"确定性"维度的守门人。它用最小的介入成本(只读扫描、无门禁、无自动写),把"测试是不是真的稳定"从一个模糊的担忧,转化为可复现的三级判定:无抖动、疑似抖动、确认抖动。其双模式分析设计让它在项目早期(无历史数据)和成熟期(有完整历史)都能有效工作,而严格的证据分级(源码模式永远只能 SUSPECT、CONFIRMED 只认历史)保证了结论的严谨性。对于任何用 AI 代理驱动的游戏开发流水线,这都是一份值得直接复用的抖动测试检测协议。
【免费下载链接】Claude-Code-Game-StudiosTurn Claude Code into a full game dev studio — 49 AI agents, 72 workflow skills, and a complete coordination system mirroring real studio hierarchy.项目地址: https://gitcode.com/GitHub_Trending/cl/Claude-Code-Game-Studios
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考