Claude
Skill
core
qa-skills 共享知识库,安装依赖单元(非触发 skill):承载被其余 10 个 skill 以相对路径引用的方法、规则、模板与脚本(可执行性标准、证据分级、风险模型、类型决策矩阵等)。仅在 qa-skills 系列 skill 工作流中被引用读取;任何具体测试任务都不要独立触发本 skill,独立使用无意义。通过 npx skills 等安装器单独安装其他 qa-skills skill 时,必须同时安装本 skill,否则引用路径断裂。
Virus-scanned
Reviewed automatically before listing.
Download
fishzjp-qa-skills-skills_core-9d93d04.zip · 84 KB
Install
skills CLI
npx skills add https://github.com/fishzjp/qa-skills/tree/main/skills/core
Claude Code
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install fishzjp-qa-skills@llmmart
Git
git clone https://github.com/fishzjp/qa-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole fishzjp/qa-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
qa-skills 共享知识库(core)
本目录是框架的公共层:不定义工作流、不面向用户触发,仅被 skills/ 下其余
12 个 skill(qa / requirement-analysis / test-strategy / …)以相对路径
../core/<file> 按需引用。单一维护源,多 skill 复用。
共享文档
| 文件 | 内容 |
|---|---|
evidence.md |
证据分级标准(E0–E4)与状态标注 |
risk-model.md |
风险评级模型(评级强制挂证据) |
executability.md |
用例可执行性硬标准(8 条,一票否决项) |
testing-principles.md |
测试原则 |
case-format.md |
用例编号与格式规范 |
coverage.md |
覆盖评估口径 |
schema-extraction.md |
schema.yaml 抽取规则 |
clarify-pattern.md |
澄清检查点模式 |
test-type-matrix.md |
类型决策矩阵(十轴全轴必答) |
triage.md |
失败分流规范(四分类判定树 + G/S 信号 + 反吞没条款,失败 ≥3 条触发) |
pipeline-integration.md |
流水线集成与非交互运行约定(⏸ 检查点降级、产物落盘、退出码语义、CI 触发分层与失败回流) |
report-template.md |
测试报告模板 |
methods/*.md |
设计方法细则(边界 / 数据驱动 / 权限 / 状态机)与执行层造数模式(数据工厂) |
scripts/*.py |
Schema 校验器、类型信号扫描器 |
When NOT to Use
- 任何具体测试任务(写用例、定策略、查 Bug 等)都不要触发本 skill: 它不包含工作流,独立使用没有产出。对应任务请用其余 12 个 skill 之一。
- 本 skill 仅在两种情况下被触及:① 其余 skill 工作流按需引用上述文件; ② 安装/校验场景(作为安装依赖单元被安装器识别)。
Files (qa-skills)
-
methods
-
boundary.md 2.9 KB
# 边界值与输入校验方法(boundary) > 输入输出型功能的用例设计方法(何时选它见 `../testing-principles.md` 第 2 节)。 > > **定位:本文件是方法参考,不是执行点。**强制执行的边界纪律已内联到 > test-case-writing 阶段三「维度核对」第 2 条(每条时间类规则至少 1 条双侧边界用例) > 与第 3 条(负向底数);数值类边界的七点公式与等价类划分按本文件加深,供强模型/高价值项目选用。 > (依据:实测注入本知识文件不改变弱模型行为——约束必须在生成路径内联才生效。) ## 1. 等价类划分 每条输入规则先划**有效等价类**与**无效等价类**,每个等价类至少一条用例: ```text 规则示例:单笔提现金额为 10–50000 元整数 有效类:10(最小)、50000(最大)、1000(常规) 无效类:9、50001、0、-5、0.5(小数)、"abc"、空、超大数(1e10) ``` ## 2. 边界值取值公式 对每个数值/长度/数量边界,取七点: ```text 空 / 最小值 / 最小-1 / 最小+1 / 最大值 / 最大+1 / 超大值 ``` 字符串长度、列表条数、文件大小、分页页码同理。**边界依据**必须来自文档或代码(常量、校验规则所在 `文件:行`,落附录),不得凭通用经验猜边界。 ## 3. 校验规则 × 写入路径(二阶交叉,高发 bug 区) 同一条校验规则必须在**每条写入路径**上分别验证——校验常只挂在创建,编辑/重提路径绕过校验是最高发的真实 bug: ```text 写入路径:创建 / 编辑 / 重新提交 / 导入 / 批量修改 / API 直调 校验规则:每条规则一行 逐格问:这条规则在这条路径上验过吗? ``` ## 4. 常见边界清单(逐项过一遍,适用则配用例) | 类别 | 检查点 | |------|--------| | 数值 | 0 / 负数 / 小数位 / 进制 / 单位(分 vs 元)/ 溢出 | | 字符串 | 空串 / 纯空格 / 超长 / 特殊字符(emoji、引号、`<script>`)/ 全半角 / 不可见字符 | | 时间 | 时区 / 跨天 / 闰年 2-29 / 结束早于开始 / 1970 / 2038 / 未来超远日期 | | 集合 | 空列表 / 单元素 / 去重 / 排序稳定性 / 重复元素 | | 文件 | 0 字节 / 超大 / 类型伪装(改后缀)/ 损坏内容 | | 编码 | UTF-8 中文 / 生僻字 / 组合字符 / 不同换行符 | ## 5. 校验用例写法要点 - **拦截类用例**预期结果必须写具体:提示文案(或文案关键字)、拦截后表单数据是否保留、拦截后能否修改重提 - **边界值用例**逐值一条,不合并("输入 0 和 -5 均报错"是两条用例) - 拦截之后配**重试恢复**用例(拦截 → 改正 → 提交成功),见 `../testing-principles.md` 第 3 节 - 服务端校验与前端校验分别验证:前端绕过(直接调 API / 改请求)时服务端仍应拦截——前端拦了不代表后端拦了 -
data-driven.md 3.7 KB
# 数据驱动与参数矩阵方法(data-driven) > API、导入类、数据转换类功能的用例设计方法(何时选它见 `../testing-principles.md` 第 2 节)。 ## 1. API 参数矩阵 每个接口一张参数表,逐参数 × 逐属性配用例: ```text 参数属性:必填/选填、类型、长度、取值范围、默认值、枚举值、组合约束 逐格产出:正常值(每属性至少一个)/ 缺失(必填报错)/ 类型错误 / 边界值(见 boundary.md) ``` - 响应侧:成功结构、每个错误码的触发条件与响应体、鉴权失败 / 越权 / 限流的表现 - 幂等:重复提交同一请求(相同业务键)→ 结果不变、不重复创建 - 并发:同一资源的并发写 → 无中间态、无互相覆盖 - 参数依据来自 API 文档或代码的校验逻辑(`文件:行` 落附录);文档与代码不一致记文档偏离 ## 2. 多参数组合降档策略(跨参数交互的正解) 单参数逐属性验证(必填/类型/边界)是正交的、必须做全;组合测试的对象是**跨参数交互**——跨字段业务规则与参数依赖。组合数随参数个数与取值数**指数增长**,必须显式选档,不静默收缩: ```text 全组合数 = ∏(各参数取值个数) 档位 适用条件 覆盖语义 全组合 全组合数 ≤ 20 且挂 Critical 风险 每个取值组合一条用例 成对组合 默认档(全组合数 > 20) 任意两参数的取值对至少出现一次 风险挑选 成对仍不可行(参数面过大/预算受限) 按 risk_ref 挑参数 × 边界值组合 ``` - **成对组合(pairwise)依据**:NIST 实证研究(Kuhn et al. 2004)——多数参数交互类缺陷(约 70% 以上)可被两两取值组合捕获,成本从指数级降为近线性 - **已知业务规则优先于机械组合**:跨字段规则("使用门槛不能低于面额"类)是显式约束——在**合法取值集**上做组合;违反规则的格子改取合法值或拆为显式负向用例,不做隐式非法组合 - **三路交互手动补**:成对组合不保证三个参数同时取特定值的组合——挂 Critical 风险的三路交互(如"金额 × 门槛 × 状态"共同决定资损路径)逐条识别、手动补齐 - **降档留痕**:降到哪档、依据(组合数 / 风险 / 预算)、被排除的组合面写进 rationale——供 `test-case-review` 追问,不静默降档 ## 3. 导入类功能 - **格式**:标准格式成功 / 字段缺失 / 类型非法 / 编码问题(GBK/UTF-8)/ 分隔符变体 - **行级处理**:全部有效 / 部分无效(行级报错还是整批失败?依据文档或实现)/ 全部无效 - **重复导入**:同一文件导入两次 → 幂等行为(跳过 / 更新 / 报错,按声明的规则断言) - **大数据量**:上限条数 / 超限表现 / 异步导入的状态可追踪与失败恢复 ## 4. 数据转换(Input → Transform → Output) - 每类典型输入一条转换用例,断言输出逐字段正确 - **读回一致**:写入后读回,值与写入一致(保真) - 畸形输入:空输入 / 部分字段 / 未知枚举值的兜底行为 - 转换规则变更后,存量已转换数据是否需要重算(一致性用例) ## 5. test_data 具体化要求 - 数据驱动不等于占位符:每条用例的输入数据**必须具体**(真实编号、真实字段值),整批数据在导读区「标准测试数据」或附录集中声明 - 需要预置的数据(库里的存量记录)在前置条件写明来源:谁造 / 哪个脚本 / 哪个接口 - 多套数据之间的差异点在用例名或前置里点明("高门槛券" vs "低门槛券"),执行者不需要自己推断 -
data-factory.md 3.6 KB
# 测试数据工厂(data-factory) > 自动化测试(UI / API)的执行层造数工程模式(用例设计层的参数化方法见 `data-driven.md`, > 两者分工:data-driven 决定"测哪些数据组合",本文件决定"执行时数据怎么造、怎么清")。 > 承接两处纪律:api-testing"自建自清理"、automated-e2e-testing"每条 test 独立"。 ## 1. 构造器模式(makeX + overrides) 每类业务实体写一个构造函数,默认值收拢一处,用例只声明差异: ```typescript // tests/factories/project.ts —— 默认值集中,测试只覆盖关心的字段 const baseProject = { type: 'normal', quota: 100, status: 'draft' }; export function makeProject(overrides: Partial<typeof baseProject> = {}) { const name = overrides.name ?? `auto-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`; return { ...baseProject, ...overrides, name }; } // 用例里:makeProject({ quota: 0 })——意图即文档,默认值变更不牵连全库用例 ``` ```python # api-tests/factories/coupon.py —— 同构 Python 形态 def make_coupon(**overrides): base = {"type": "normal", "quota": 100, "status": "draft"} base.update(overrides) base.setdefault("name", f"auto-{int(time.time()*1000)}") return base ``` - **默认值必须合规**:逐字段核对材料约束(maxLength / 枚举 / 格式 / 跨字段规则)—— api-testing 的"模板总长 ≤ 约束上限 −2"在本层统一执行,构造器是唯一出口,用例不得散写裸数据 - **唯一性在构造器内保证**:唯一字段(名称/编号)由构造器生成,不靠用例自觉 ## 2. 造数通道三选一 | 通道 | 适用 | 失效条件 / 约束 | |---|---|---| | **API 造数**(默认) | 被测系统有可用接口、鉴权可自动化 | 接口参数受限造不出的形态(如不可通过 API 设置的内部状态)→ 降 DB | | **DB 快照 / fixture** | 大前置数据(存量记录、复杂关联)、只读场景、API 造不出的状态 | 快照含易变字段(时间戳/序列)要声明;写库绕过业务校验,可能造出业务上非法的状态——仅用于"存量脏数据"类被测场景 | | **UI 造数** | 被测的就是录入流程本身;或无 API 可用 | 最慢最脆,仅此两情形可用;能 API 化的前置一律 API 化 | 选择原则:**前置造数永远选最快最稳的通道,被测行为才走最真实的通道**。前置用 UI 走完整表单 = 给失败率和时长上税。 ## 3. 按执行隔离与清理 - **前缀 = 用例编号**(如 `TC-01-03-`):动态数据统一 `TC编号-语义-随机段` 命名—— 失败后可按前缀识别归属,也支持会话尾批量扫尾 - **清理三道防线**:① `afterEach` 精确清理(api-testing/automated-e2e-testing 既有纪律); ② 清理失败 `.catch()` 记 warning 不阻塞下条(噪音不吞错);③ 会话尾按前缀批量扫尾 (列表接口/API 按前缀过滤删除)——兜住 ① 漏网的 - **断言隔离**:查询/列表类断言永远先造自己的数据再断言其上,不假设库里有(或没有) 任何存量数据——存量不确定性是 flaky 的最大来源之一 ## 4. 与既有纪律的衔接 - api-testing"测试数据自建自清理(setup 创建 / teardown 删除)"→ 本文件第 1/3 节是其工程化落地 - automated-e2e-testing"每条 test 独立(自建数据 + 自清理)"+ 速查表"唯一命名"→ 前缀升级见第 3 节 - 跨字段业务规则回检("使用门槛不能低于面额"类)→ 构造器默认值合法,overrides 后**逐格回检** (api-testing §3 既有纪律),非法组合拆显式负向用例 -
permission.md 1.8 KB
# 权限测试方法(permission) > 权限系统 / 多角色功能的用例设计方法(何时选它见 `../testing-principles.md` 第 2 节)。 ## 1. Role × Action × Resource 矩阵 列出全部角色 × 全部操作 × 关键资源,逐格核对: ```text 角色:管理员 / 运营 / 普通用户 / 游客(未登录) 操作:查看 / 创建 / 编辑 / 删除 / 导出 / 审批 资源:自己的数据 / 他人数据 / 全局配置 逐格三态:允许(配正向用例)/ 拒绝(配拦截用例)/ 未定义(列入澄清问题) ``` - 权限依据来自文档的角色定义或代码的鉴权逻辑(`文件:行` 落附录),矩阵中每格的判定要有出处 - 矩阵先在澄清阶段与用户核对,未定义格不得自行假设 ## 2. 两类越权必测 - **垂直越权**:低权限角色调用高权限接口/入口(普通用户直接调管理员接口)→ 拒绝 - **水平越权**:同权限角色访问他人资源(用户 A 查看/编辑用户 B 的数据,改 URL 中的 id)→ 拒绝且无数据泄漏 前端隐藏不等于后端拦截:**绕过 UI 直接调 API** 的越权用例必须配(协作用例或 API 用例)。 ## 3. 权限变更即时性 - 角色提升/降级后,旧权限是否立即失效(已登录会话、已打开页面、缓存的菜单) - 权限回收后,进行中的操作(草稿、锁定的资源)如何处置 ## 4. 无权限的表现 - 未授权操作的提示文案与引导(登录引导 / 申请权限引导) - 部分授权(有查看无编辑)时界面元素的隐藏/置灰/点击提示,三态逐一验证 ## 5. 用例组织 权限作为**独立模块**(横切关注点),不分散在各功能模块里;矩阵的每一行(角色)至少一条正向 + 一条拦截用例。 -
state-machine.md 2.4 KB
# 状态机驱动组织方法(state-machine) > 被测功能有**明显状态流转**时的用例组织方法(何时选它见 `../testing-principles.md` 第 2 节)。状态机驱动能发现更多遗漏的边界场景。 ## 1. 提取状态机 从需求/技术方案/代码中提取核心状态机,逐项确认: ```text 状态集:{待领取、标注中、已提交、已通过、已驳回、已过期} 事件集:{领取、提交、通过、驳回、超时} 转换边:状态A --事件--> 状态B(逐条列出,含守卫条件) ``` - 每条边必须有依据(文档章节或 `文件:行`,按 `../evidence.md` 标注) - 代码模式下从代码的状态字段与分支提取**实际**状态机,与文档状态机对照——缺失的边、多出的边都是发现(文档偏离 / 死代码) - 状态机图以文字清单落盘(`状态A --事件--> 状态B` 一行一条),不要求画图 ## 2. 按状态节点分模块 每个状态节点一个模块,模块内覆盖四类内容: 1. **进入条件**:什么操作/事件让对象进入此状态(含从哪些状态可以到达) 2. **操作行为**:此状态下允许的操作、可见的数据、界面表现 3. **转换规则**:此状态下每个事件触发后转向哪里;守卫条件(如"仅管理员可驳回") 4. **异常情况**:非法事件(此状态下不允许的操作)、超时、并发冲突 ## 3. 每条边一个用例 + 三类必补场景 - 一阶主干:**每条状态边至少一条用例**(`A --事件--> B`:处于 A、触发事件、断言到 B 且伴随现象正确) - **非法转换**:此状态下触发不允许的事件 → 断言拒绝且状态不变 - **并发/竞态**:两个操作同时作用于同一对象(如管理员驳回的同时标注员提交)→ 断言状态一致、无中间态泄漏 - **逆向边**:有回退/撤销语义的边(已提交 → 驳回 → 重新编辑)单独成用例,重点验证回退后数据是否保留/重置 ## 4. 横切关注点独立模块 权限(谁能触发哪些事件)、通知(哪些边触发消息)、数据一致性(状态变更时关联数据级联)作为独立模块补充,不混入状态模块。 ## 5. 何时不用状态机组织 无明显状态流转的功能(纯输入校验、查询、数据转换)按 `boundary.md` / `data-driven.md` 组织,不要硬造状态机。
-
-
scripts
-
scan_signals.py 19.3 KB
#!/usr/bin/env python3 """类型域 G 级信号扫描器 + 决策预填表生成器. 配套 core/test-type-matrix.md:G 级(greppable)信号由本脚本机械扫描产出; S 级(semantic)信号(矩阵各轴清单中标〔S〕的项)由 agent 读代码复核, 本脚本不负责。预填仅基于 G 级;exclude 永不预填(防橡皮图章)。 用法: python3 scan_signals.py <仓库路径> [--out 输出文件.yml] 产出(stdout 或 --out 落盘,YAML): scan_meta 仓库、扫描规模、矩阵版本、截断标志(truncated) g_signals 每轴 G 级命中(文件:行 + 标签 + 摘要,逐轴截断) prefill 修订起点(非决策):有 G 命中 → include 候选;无 G 命中 → 待复核 确定性保证:目录与命中均排序输出,同一仓库重复运行结果一致。 零第三方依赖;跳过依赖/构建产物目录、二进制与超大文件。 """ import argparse import os import re import stat as stat_mod import sys from pathlib import Path MATRIX_VERSION = "v1" MAX_FILE_BYTES = 1_000_000 MAX_FILES = 30_000 MAX_HITS_PER_LABEL = 15 SKIP_DIRS = { ".git", ".svn", ".hg", ".idea", ".vscode", "__pycache__", "node_modules", "dist", "build", "vendor", "target", "coverage", ".venv", "venv", ".tox", ".mypy_cache", ".pytest_cache", "obj", "bin", "out", } SKIP_FILE_SUFFIXES = ( ".min.js", ".min.css", ".map", ".lock", "package-lock.json", "yarn.lock", "pnpm-lock.yaml", "go.sum", "poetry.lock", ) TEXT_EXTS = { ".py", ".js", ".jsx", ".ts", ".tsx", ".vue", ".svelte", ".java", ".go", ".rs", ".rb", ".php", ".cs", ".kt", ".swift", ".scala", ".dart", ".sql", ".sh", ".yaml", ".yml", ".json", ".toml", ".xml", ".html", ".proto", ".gradle", ".kts", ".properties", ".cfg", ".ini", ".j2", ".tf", ".wxml", } # 无后缀但有固定文件名的配置文本(如 iOS CocoaPods 清单) TEXT_FILE_NAMES = {"Podfile"} def _casefold_charclass(word): """把小写词展开为逐字符大小写皆可的字符类序列(kafka → [Kk][Aa][Ff][Kk][Aa]). 用于 MQ 词表边界放宽:re.I 会污染边界断言中的字符类([a-z] 在 re.I 下 连大写也匹配),使「后非小写字母」的 camelCase 判定无法表达;显式 casefold 展开后可脱离 re.I,保留组合标识符友好的自定义边界(见「消息队列消费与 ack」)。 """ return "".join(f"[{c}{c.upper()}]" for c in word) # MQ 词表 casefold 展开结果(供 AXIS_PATTERNS 内 f-string 引用) _MQ_TOKENS = "|".join( _casefold_charclass(w) for w in ("kafka", "rabbitmq", "rocketmq", "amqp", "pulsar", "bullmq") ) # 每轴 G 级内容信号:(标签, 正则)。正则命中 ≠ 结论成立——预填表仅供 agent 修订。 AXIS_PATTERNS = { "performance": [ ("缓存依赖", re.compile(r"\b(redis|memcach\w*|@(?:Cacheable|CacheEvict|CachePut)|cache\.(?:get|set|del))\b", re.I)), ("无分页全量查询", re.compile(r"SELECT\s+\*\s+FROM", re.I)), # 「消息队列」已迁 reliability 轴「消息队列消费与 ack」(对齐矩阵轴3口径); # 轴1 剩余的 MQ 关切(积压/消费速率)是 S 级语义项,由 agent 读代码复核承载 ], "security_business": [ ("鉴权机制存在", re.compile(r"auth(?!or)\w*|jwt|oauth\w*|rbac|@PreAuthorize|hasRole|requireLogin|鉴权|登录校验", re.I)), ("PII 字段", re.compile(r"id_?card|idcard|身份证|手机号|\b(phone|mobile|ssn)\b", re.I)), ("可枚举资源 ID", re.compile(r"/\{[a-zA-Z_]*[iI]d\}|:id\b|@PathVariable[^\n]*[iI]d")), ("疑似 SQL 拼接", re.compile(r"""f["'](SELECT|INSERT|UPDATE|DELETE)|execute\([^)\n]{0,60}(SELECT|INSERT|UPDATE|DELETE)[^)\n]{0,40}(\+|%s)""", re.I)), ], "reliability": [ ("重试逻辑", re.compile(r"retry\w*|backoff|max_?retries|重试", re.I)), ("超时配置", re.compile(r"\b(timeout|deadline|context\.WithTimeout|TimeSpan\.From)\b", re.I)), # 矩阵轴3 口径「消息队列消费与 ack」:broker 存在性 + 显式 ack 语义; # celery/sidekiq 由下方「异步任务/调度」覆盖,此处不重复挂载。 # 词表边界用双 lookaround(前非任意字母 / 后非小写字母)替代 \b: # AMQP_URL / rabbitmq_conn / KafkaProducer 等组合标识符可命中, # kafkaesque / pykafka 等小写粘连异义词仍排除(\b 对前者天然漏检)。 # 本标签不用 re.I:词表经 _casefold_charclass 展开自带大小写, # 注解名(Java PascalCase 固定形态)与 basic_ack(协议恒小写)本就精确 ("消息队列消费与 ack", re.compile( rf"(?<![A-Za-z])(?:{_MQ_TOKENS})(?![a-z])" r"|@(?:KafkaListener|RabbitListener|JmsListener)" r"|basic_(?:ack|nack)\b")), ("异步任务/调度", re.compile(r"\b(celery|sidekiq|xxl-?job|quartz|cron\w*|scheduler\w*)\b", re.I)), ("断路器", re.compile(r"\b(circuit[_\s-]?breaker|hystrix|resilience4j|sentinel)\b", re.I)), ("事务/补偿", re.compile(r"\b(@Transactional|begin[_\s]?transaction|rollback|compensat\w*|saga)\b", re.I)), ], "concurrency": [ ("锁与同步原语", re.compile(r"\b(sync\.(?:RWMutex|Mutex)|synchronized|ReentrantLock|threading\.Lock|FOR\s+UPDATE|with_for_update)\b", re.I)), ("库存/名额/配额", re.compile(r"stock|inventory|库存|扣减|deduct\w*|seckill|秒杀|quota|配额", re.I)), ("唯一性约束", re.compile(r"\b(unique[_\s-]?(?:constraint|key|index)|UNIQUE\s+KEY|unique=True)\b", re.I)), ], "compatibility": [ ("浏览器特性 API", re.compile(r"\b(IntersectionObserver|ResizeObserver|navigator\.\w+|matchMedia)\b")), ("响应式断点", re.compile(r"@media|useBreakpoint|responsive")), # 移动端多端信号:RN/Flutter/小程序的平台分支是"同功能跨端行为可能不一致"的 G 级证据 ("多端平台分支", re.compile( r"\b(Platform\.OS\s*[=!<>]=?|kIsWeb|defaultTargetPlatform\b|TargetPlatform\.(?:iOS|android|macOS)" r"|wx\.getSystemInfo\w*|my\.getSystemInfo(?:Sync)?|tt\.getSystemInfo\w*" r"|process\.env\.TARO_ENV|UNI_PLATFORM|#ifdef\s+(?:APP-PLUS|MP-WEIXIN|MP-ALIPAY|H5))", re.I)), ("系统版本门槛", re.compile( r"\b(minSdkVersion|targetSdkVersion|compileSdkVersion|platform\s*:\s*(?:ios|osx)" r"|IPHONEOS_DEPLOYMENT_TARGET|MinimumOSVersion|deployment_target|minifyEnabled)\b", re.I)), ], "accessibility": [ ("无障碍线索", re.compile(r"\b(aria-[\w-]+|role=|tabindex|focus-visible|<img[^>]*alt)\b", re.I)), ], "visual": [ ("已有截图测试", re.compile(r"toHaveScreenshot|screenshot\w*|视觉回归|visual[_\s-]?regression", re.I)), ], "i18n": [ ("i18n 框架/格式化", re.compile(r"\b(i18next|vue-i18n|react-intl|formatjs|gettext|Intl\.(?:DateTime|Number)Format|dayjs|date-fns|moment)\b", re.I)), # 矩阵轴8 口径「时区处理逻辑」:显式时区 token 即 G 级线索(结论仍须 agent 复核) ("时区处理", re.compile( r"\b(?:UTC|timezone\w*|time[\s_-]?zone\w*|ZoneId|ZonedDateTime|OffsetDateTime" r"|pytz|tzdata|dateutil\.tz|utcOffset)\b|时区|\.tz\(", re.I)), ], "migration": [ ("DDL/回填", re.compile(r"ALTER\s+TABLE|CREATE\s+(?:UNIQUE\s+)?INDEX|backfill|回填|db[_\s-]?migrat\w*", re.I)), ("多版本 API", re.compile(r"/v[2-9](?:/|\b)|api[_\s-]?version", re.I)), ], "contract_integration": [ ("疑似外部调用", re.compile(r"\b(requests\.(?:get|post|put|delete)|http\.(?:Get|Post)|axios\.(?:get|post)|RestTemplate|WebClient|grpc\.(?:Dial|NewClient))\b")), ("契约定义", re.compile(r"\b(openapi|swagger|graphql)\b", re.I)), # 消息契约(矩阵轴 10 G 级信号,此前仅文档声明无实现):AsyncAPI / Avro(.avsc) # / Pact 契约 / Schema Registry 客户端与配置——按文件内容词匹配 ("消息契约", re.compile(r"\b(asyncapi|avro|avsc|pact\b|schema[_\s-]?registry)\b", re.I)), ("Webhook/回调", re.compile(r"webhook|callback[_\s-]?url|回调", re.I)), ], } # 每轴 G 级路径信号:(标签, 相对路径匹配正则)。 AXIS_PATH_PATTERNS = { "compatibility": [ # 移动端/小程序工程标志:存在即意味着跨 OS 版本与设备形态的兼容面(此前扫描器对 # RN/Flutter/小程序全盲,移动端项目这三个轴会全部落"无命中"的假阴性) ("移动端工程配置", re.compile( r"(^|/)(android/app/build\.gradle(?:\.kts)?|ios/Podfile|pubspec\.yaml" r"|project\.config\.json|capacitor\.config\.[jt]s|Info\.plist)$", re.I)), ], "migration": [ ("migration 目录", re.compile(r"(^|/)(migrations?|alembic|flyway|liquibase|db/migrate)/", re.I)), # .sql 不再全局兜底:根目录散落的 init.sql / dump.sql 等无升级语义, # 仅当位于迁移目录上下文(含其子层)内才计为独立文件信号——对齐矩阵轴9口径 ("migration 文件", re.compile( r"(^|/)(migrations?|alembic|flyway|liquibase|db/migrate)/(?:.*/)?[^/]*\.sql$", re.I)), ], "i18n": [ ("locale 资源目录", re.compile(r"(^|/)(locales?|i18n|lang)/[^/]+\.(?:json|ya?ml|ts|po)$", re.I)), ("gettext 资源", re.compile(r"\.po$", re.I)), ], "contract_integration": [ ("proto 契约", re.compile(r"\.proto$", re.I)), ("消息契约文件", re.compile(r"\.avsc$", re.I)), ], } # 前端/客户端存在性(轴 5/6/7 的最低信号,标签固定为「有前端」——矩阵文档按此引用): # package.json 含 Web 前端或跨端框架依赖 / pubspec.yaml(Flutter/Dart) / 小程序工程配置, # 或前端源码文件达到阈值。覆盖 Web、React Native、Flutter、Taro/uni-app 与各系小程序。 FE_DEP_RE = re.compile( r'"(react|vue|angular|svelte|next|nuxt|vite|webpack|react-native|@tarojs/taro' r'|@dcloudio/[a-z-]+|uni-app|mpvue|wepy)"', re.I) FLUTTER_MARKER_NAME = "pubspec.yaml" # Flutter/Dart 工程唯一入口文件,出现即视作客户端信号 MINI_PROGRAM_CONFIGS = {"project.config.json"} # 微信/字节等小程序工程标志配置 # 样式族(css/scss/less)计入前端源码计数:≥3 个样式文件几乎必然是前端工程, # 且轴5/6/7 的探测依赖「有前端」信号点亮;.min.css 产物已在 SKIP_FILE_SUFFIXES 排除 FE_EXT_RE = re.compile(r"\.(vue|svelte|html|jsx|tsx|dart|wxml|css|scss|less)$", re.I) FE_FILE_THRESHOLD = 3 AXES = ["performance", "security_business", "reliability", "concurrency", "compatibility", "accessibility", "visual", "i18n", "migration", "contract_integration"] AXIS_NAMES = { "performance": "性能效率", "security_business": "业务安全", "reliability": "可靠性", "concurrency": "并发一致性", "compatibility": "兼容性", "accessibility": "无障碍", "visual": "视觉一致性", "i18n": "国际化", "migration": "迁移与升级", "contract_integration": "契约与集成", } def iter_files(root: Path, state=None): """产出 (path, rel)。os.walk topdown 剪枝 SKIP_DIRS;达到 MAX_FILES 截断时置 state['truncated']。""" n = 0 for dirpath, dirnames, filenames in os.walk(root): dirnames[:] = sorted(d for d in dirnames if d not in SKIP_DIRS) for name in sorted(filenames): if n >= MAX_FILES: if state is not None: state["truncated"] = True return rel = (Path(dirpath) / name).relative_to(root).as_posix() if any(rel.endswith(s) for s in SKIP_FILE_SUFFIXES): continue n += 1 yield Path(dirpath) / name, rel def sanitize(text: str) -> str: # 顺序是硬约束:先清洗控制字符、先截断、剥尾部反斜杠,最后才转义—— # 截断若落在转义对中间会留下奇数个尾部反斜杠,使 YAML 双引号标量解析失败 # C0(< 空格)、DEL 与 C1(U+007F–U+009F)控制字符全部置换为空格——YAML 双引号标量均不接受 text = "".join(ch if (ch >= " " and not ("\x7f" <= ch <= "\x9f")) else " " for ch in text.strip()) return text[:80].rstrip("\\").replace("\\", "\\\\").replace('"', "'") def yq(s: str) -> str: return '"' + sanitize(s) + '"' def scan(root: Path): hits = {axis: [] for axis in AXES} # (label, file, line, text) label_counts = {} # (axis,label) -> n fe_dep_found = False fl_pubspec_found = False mp_config_found = False fe_ext_count = 0 files_scanned = 0 fallback_files = 0 # 非 UTF-8 源文件数(gb18030 回退读取;中文信号可能部分受损,须在 meta 披露) state = {"truncated": False} for path, rel in iter_files(root, state): files_scanned += 1 # 路径信号(同样受 MAX_HITS_PER_LABEL 上限,与内容信号口径一致) for axis, rules in AXIS_PATH_PATTERNS.items(): for label, rx in rules: key = (axis, label) if label_counts.get(key, 0) >= MAX_HITS_PER_LABEL: continue if rx.search(rel): hits[axis].append((label, rel, 0, "(路径命中)")) label_counts[key] = label_counts.get(key, 0) + 1 # 前端/客户端存在性(文件名级判据:Flutter / 小程序不依赖内容即可判定) if FE_EXT_RE.search(rel): fe_ext_count += 1 if path.name == FLUTTER_MARKER_NAME: fl_pubspec_found = True if path.name in MINI_PROGRAM_CONFIGS: mp_config_found = True # 内容信号(扩展名白名单之外,再放行 Podfile 这类无后缀固定名配置) if (path.suffix.lower() not in TEXT_EXTS and path.name not in TEXT_FILE_NAMES): continue try: st = path.stat() if not stat_mod.S_ISREG(st.st_mode): continue # 字符设备/套接字等(如指向 /dev/zero 的符号链接)不可 read_text if st.st_size > MAX_FILE_BYTES: continue try: text = path.read_text(encoding="utf-8") except UnicodeDecodeError: # errors="ignore" 会静默丢弃非 UTF-8 字节——GBK/GB18030 老仓库的中文注释信号 # (PII/库存/秒杀等 CJK 模式)将整轴假阴性,故显式回退而非忽略 text = path.read_text(encoding="gb18030", errors="replace") fallback_files += 1 except OSError: continue if path.name == "package.json" and FE_DEP_RE.search(text): fe_dep_found = True for axis, rules in AXIS_PATTERNS.items(): for label, rx in rules: key = (axis, label) if label_counts.get(key, 0) >= MAX_HITS_PER_LABEL: continue for no, line in enumerate(text.split("\n"), 1): if label_counts.get(key, 0) >= MAX_HITS_PER_LABEL: break if rx.search(line): hits[axis].append((label, rel, no, line)) label_counts[key] = label_counts.get(key, 0) + 1 if (fe_dep_found or fl_pubspec_found or mp_config_found or fe_ext_count >= FE_FILE_THRESHOLD): signal = ("前端/客户端存在(NPM 前端/RN/跨端依赖=" + str(fe_dep_found) + ", Flutter(pubspec)=" + str(fl_pubspec_found) + ", 小程序工程配置=" + str(mp_config_found) + ", 前端源码文件≥" + str(FE_FILE_THRESHOLD) + ":" + str(fe_ext_count) + ")") for axis in ("compatibility", "accessibility", "visual"): hits[axis].append(("有前端", "(repo)", 0, signal)) # 去重(同轴同标签同文件同行只留一条)并按轴内 (label, file, line) 排序 for axis in AXES: seen, uniq = set(), [] for h in hits[axis]: k = (h[0], h[1], h[2]) if k not in seen: seen.add(k) uniq.append(h) hits[axis] = sorted(uniq, key=lambda h: (h[0], h[1], h[2])) return hits, files_scanned, fallback_files, state["truncated"] def render_yaml(hits, files_scanned, fallback_files: int, repo: str, truncated: bool) -> str: out = [] out.append("# G 级信号扫描结果 + 决策预填表(修订起点,不是决策)") out.append("# 配套 core/test-type-matrix.md;exclude 永不预填,需 agent 完成 S 级复核与需求信号判定") out.append("scan_meta:") out.append(f" repo: {yq(repo)}") out.append(f" matrix_version: {yq(MATRIX_VERSION)}") out.append(f" files_considered: {files_scanned}") out.append(f" truncated: {'true' if truncated else 'false'}") out.append(f" decode_fallback_files: {fallback_files} # 非 UTF-8 以 gb18030 回退读取的文件数(>0 时中文信号可能有损,人工抽查源文件编码)") out.append(" notes:") out.append(" - 命中是线索不是结论;逐轴按矩阵六字段决策,预填行仅供修订") out.append(" - security_business 为硬默认轴(Web/API 一律至少 standard)") if truncated: out.append(f" - 文件数超过 {MAX_FILES} 上限已提前截断——未扫描文件不产生信号," "exclude 决策不可仅凭本清单") out.append("") out.append("g_signals:") for axis in AXES: out.append(f" {axis}: # {AXIS_NAMES[axis]}") if not hits[axis]: out.append(" []") continue for label, rel, no, text in hits[axis]: loc = f"{rel}:{no}" if no else rel out.append(f" - {{label: {yq(label)}, loc: {yq(loc)}, text: {yq(text)}}}") out.append("") out.append("prefill: # 修订起点:有 G 命中 → include 候选;无 G 命中 → 待复核(非 exclude)") for axis in AXES: n = len(hits[axis]) if axis == "security_business": suggest = "include候选(硬默认:Web/API 一律至少 standard)" elif n: suggest = "include候选(核对需求信号与 S 级后定 depth)" else: suggest = "待复核(需求信号 + S 级复核后才可决策;exclude 需 G+S 双清单)" out.append(f" {axis}: {{g_hits: {n}, suggest: {yq(suggest)}}}") return "\n".join(out) + "\n" def main(): ap = argparse.ArgumentParser(description="类型域 G 级信号扫描") ap.add_argument("repo", help="被测仓库路径") ap.add_argument("--out", help="结果落盘路径(缺省打印到 stdout)") args = ap.parse_args() root = Path(args.repo) if not root.is_dir(): print(f"错误: 仓库路径不存在或不是目录 {root}", file=sys.stderr) return 1 hits, files_scanned, fallback_files, truncated = scan(root) yaml_text = render_yaml(hits, files_scanned, fallback_files, str(root), truncated) if args.out: try: Path(args.out).write_text(yaml_text, encoding="utf-8") except OSError as e: print(f"错误: 无法写入 {args.out}: {e}", file=sys.stderr) return 1 print(f"已写入 {args.out}(考虑文件 {files_scanned})") else: print(yaml_text, end="") return 0 if __name__ == "__main__": sys.exit(main()) -
validate_schema.py 33.8 KB
#!/usr/bin/env python3 """Test Case Schema 校验器(markmap ↔ schema.yaml 一致性)+ 测试策略类型域校验(V1–V5 + 覆盖门禁). 模式一(用例 Schema,配套 core/schema-extraction.md,抽取 / 修订重抽后运行): python3 validate_schema.py 测试用例_markmap.md 测试用例.schema.yaml 1. YAML 可解析(转义纪律)——环境装有 PyYAML 时全量解析并检查枚举值; 未安装时降级为基础 lint(引号配对 / 裸引号),仍能抓住 "双引号值内裸放引号" 这类最常见的中断下游消费的解析错误 2. TC 编号一致性——schema 中的 id 必须存在于 markmap(孤儿报错), markmap 中的编号缺失于 schema(遗漏告警,增量更新场景可接受但需人工确认) 3. 占位符——title / steps / expected 等字段值出现 {xxx} / <xxx> / 「某某」即报错 (用例可执行性红线,见 core/executability.md) 4. 契约词表与结构——tags 词表外标签告警;module 编号与 TC 编号首段不一致报错; 用例级 preconditions 与顶层 modules 表共享前置重复仅告警 模式二(测试策略类型域决策,配套 core/test-type-matrix.md,test-strategy 落盘后运行): python3 validate_schema.py 测试策略.md V1 全轴必答——十轴齐全且 decision ∈ include/exclude/handoff(每轴单行 flow 风格); depth ∈ full/standard/light("不测"是范围决策不是深度值:功能域 include: false, 类型域 decision: exclude/handoff) V2 include 挂证据——signals 或 risk_refs 非空(无证据纳入 = 过测) V3 exclude 挂理由——rationale 非空 + scanned 含 G 级与 S 级双记录(无理由排除 = 漏测; 无代码仓库场景豁免 S 级);security_business 为硬默认轴(Web/API 系统无排除 出口),其 exclude 须在 rationale 声明非 Web/API 依据,否则告警级提示复核 V4 full 有预算——depth=full 轴必须挂 risk_refs;full 总数(两域合并)≤3, 超限须有 budget_review(预算裁决记录,R6>R1);depth_budget.full_axes 与实际一致。 两域 scope 可写在同一或不同 yaml 代码块,均纳入统计 V4-终态留痕——include 且挂 Critical 风险的类型域轴若 depth 未达 full,其轴名必须出现 在 budget_review 文本中(堵住"先超限触发检查点、再裁剪回 ≤3 后无迹可查"的逃逸窗口: 维持低档也须留一行依据,不许无声消失) V5 移交不断链——execution_status=blocked 必带 todo;executor 非空且非 agent 时 (含 handoff)必带 handoff_ref 模式三(可选增强参数,两模式均可叠加): --strategy 测试策略.md 仅模式一生效:从策略正文的 Risk Map 提取 Critical/High 风险编号, 校验每条均被至少一条用例的 risk_ref 反向覆盖(证据链最后一环, Critical/High 风险零覆盖 = 报错);risk_ref 指涉未知编号则告警(疑似笔误) --repo-root <仓库路径> 抽查指涉真实性:axis signals 与用例 code_refs 中形如 "path.ext:行号" 的文件必须在指定仓库下真实存在(缺失告警, 防"格式合规的幻觉证据"穿过 V2/V3 形式闸门) 退出码:0 通过(含告警)/ 1 有错误 """ import argparse import re import sys from pathlib import Path TC_RE = re.compile(r"TC-\d+-\d+") PLACEHOLDER_RE = re.compile(r"\{[^}\n]{1,40}\}|<[^>\n]{1,40}>|某[某数据个账号]") ENUMS = { "priority": {"P0", "P1", "P2"}, "type": {"functional", "boundary", "exception", "permission", "regression", "state", "data", "reliability", "concurrency", "security", "compatibility"}, "execution_model": {"ui", "dev-collab"}, "status": {"active", "changed", "deprecated"}, } TEXT_FIELDS = ("title", "steps", "expected", "preconditions", "test_data") # ---------- 三层契约词表(core/schema-extraction.md「文件整体形态」) ---------- # tags 两组合法值:可测试性标注 + 类型域轴标签;开放格式下的拼写防呆,词表外只告警 TAG_VOCAB = { "[需真机]", "[需Mock]", "[需专业环境]", "[并发]", "[可靠]", "[安全]", "[兼容]", "[迁移]", "[集成]", "[国际化]", } MODULE_NUM_RE = re.compile(r"^\s*(\d+)") # 一级模块标题首段编号("2. 营销活动" → 2) # ---------- 审计加固常量(覆盖门禁 / 终态留痕 / 指涉抽查) ---------- # 硬默认轴集合:矩阵 §轴2——Web/API 系统一律 standard,无 R4 排除出口 HARD_DEFAULT_AXES = {"security_business"} # 风险编号与等级词形:risk-model.md 权威口径为 R1…Rn + Critical/High/Medium/Low RISK_ID_RE = re.compile(r"\bR\d{1,3}\b") CRITICAL_WORD_RE = re.compile(r"\bCritical\b", re.I) HIGH_WORD_RE = re.compile(r"\bHigh\b", re.I) # 仓库内文件指涉形态:"路径/文件名.ext:行号"(signals 与 code_refs 共用) REPO_FILE_LOC_RE = re.compile( r"([\w.\-/\\]{1,120}\.(?:py|go|java|kt|swift|m|ts|tsx|js|jsx|mjs|vue|svelte|" r"rb|php|cs|scala|dart|sql|yaml|yml|json|toml|xml|proto|sh|gradle)):(\d+)", re.I) def extract_markmap_tcs(md_text): """markmap 用例编号:剔除删除线段(~~TC-xx~~ 已废弃,同行的存活编号保留)。 附录中的编号交叉引用不剔除(无可靠的附录起点判定)——正文已删而附录残留 的不一致场景交由 test-case-review 独立审查发现,不在本函数硬猜边界。 """ ids = [] for line in md_text.splitlines(): line = re.sub(r"~~[^~]+~~", "", line) # 删除线段不算遗漏,整行丢弃会漏提同行存活编号 m = re.match(r"\s*-\s*\*{0,2}(TC-\d+-\d+)", line) if m: ids.append(m.group(1)) return ids def lint_yaml_basic(text, errors): """零依赖降级 lint:抓最常见的中断解析的转义错误(引号配对 / 裸引号)。""" for no, line in enumerate(text.splitlines(), 1): code = _strip_yaml_comment(line) n_dq = code.count('"') if n_dq % 2 == 1: errors.append(f"L{no}: 双引号数量为奇数(值内裸放引号或漏转义): {line.strip()[:60]}") # key: "值" 闭合后又有非分隔符内容 = 值内裸放引号(如 "满100减20"券",最常见的中断解析错误); # 逗号/右花括号除外(flow 映射中为合法分隔) if re.search(r':\s*"(?:[^"\\]|\\.)*"[ \t]*[^\s#,}]', code): errors.append(f"L{no}: 双引号值闭合后又出现内容(疑似裸引号未转义): {line.strip()[:60]}") def _strip_yaml_comment(line): """去掉双引号外首个 # 及其后内容(引号内的 # 是值的一部分,不截断)。""" in_q = False for i, c in enumerate(line): if c == '"': in_q = not in_q elif c == "#" and not in_q: return line[:i] return line def walk_cases(node, out): """递归收集含 TC 编号 id 的映射节点。""" if isinstance(node, dict): vid = node.get("id") if isinstance(vid, str) and TC_RE.fullmatch(vid): out.append(node) for v in node.values(): walk_cases(v, out) elif isinstance(node, list): for v in node: walk_cases(v, out) def check_case(case, errors, warnings): cid = case.get("id", "?") for field, allowed in ENUMS.items(): val = case.get(field) if val is not None and val not in allowed: errors.append(f"{cid}: {field}='{val}' 不在枚举 {sorted(allowed)}") for field in TEXT_FIELDS: val = case.get(field) blobs = val if isinstance(val, list) else [val] for blob in blobs: if isinstance(blob, str): m = PLACEHOLDER_RE.search(blob) if m: errors.append(f"{cid}: {field} 含占位符 '{m.group(0)}'(可执行性红线)") # tags 词表防呆:未知标签不阻断,但提示人工确认拼写或补词表 for tag in as_list(case.get("tags")): if tag not in TAG_VOCAB: warnings.append(f"{cid}: tags 含词表外标签 '{tag}'(合法集见 schema-extraction.md tags 注释)") # module 编号与 TC 编号首段一致性:挂错模块属结构性笔误;任一侧解析不出编号则跳过 m_case = re.match(r"TC-(\d+)-\d+", str(cid)) mod = case.get("module") if m_case and isinstance(mod, str): m_mod = MODULE_NUM_RE.match(mod) if m_mod and int(m_mod.group(1)) != int(m_case.group(1)): errors.append(f"{cid}: id 模块号 {m_case.group(1)} 与 module '{mod}' 的编号不一致") def collect_shared_preconditions(doc): """读取顶层 modules 表的共享前置集合 {前置原文: 模块名};旧形态无此键时返回空表。""" out = {} for mod in (doc.get("modules") or []) if isinstance(doc, dict) else []: if isinstance(mod, dict): name = str(mod.get("module", "?")) for pre in as_list(mod.get("shared_preconditions")): out[pre] = name return out def as_list(val): """字段值统一为字符串列表(兼容 str / list / None 三种落盘形态)。""" if isinstance(val, list): return [v for v in val if isinstance(v, str)] if isinstance(val, str): return [val] return [] def extract_risk_levels_detailed(md_text): """提取({风险编号: 等级}, 有等级依据的编号集合),同号取最高级(Critical > High > 其余忽略)。 只认 Critical / High 两个门禁相关等级。识别两种书写形态(取并集): 1. 行内同现——同一行同时出现 Rn 与等级词(markdown 表格行、行内散文); 2. 跨行记录——yaml 风险块中 `id: Rn` 行与其后最近的等级词行(risk-model.md §4 权威格式的 id 与 level 即分行书写,此前逐行启发式对它整块失效 → 门禁静默放行)。 记录边界为下一个 id 行 / 代码围栏 / 标题 / 空行;嵌套块(如 evidence.level)按 缩进深于 id 行排除,不充当记录自身的等级依据。 第二返回值 = 出现过等级依据的编号:已映射 Critical/High,或记录/行内存在任意值的 level: 字段或 Medium/Low 等级词(仅 Medium/Low 属正常不入门禁,不算提取缺口)。 供"部分记录提取失败"的缺口告警使用——只看映射为空会在混合形态下继续 fail-open。 """ levels = {} leveled = set() id_line_re = re.compile(r"^\s*(?:-\s*)?id:\s*[\"']?(R\d{1,3})\b") level_field_re = re.compile(r"\blevel\s*[::]", re.I) medium_word_re = re.compile(r"\bMedium\b", re.I) low_word_re = re.compile(r"\bLow\b", re.I) def _raise(rid, lv): old = levels.get(rid) if old == "Critical" or (old == "High" and lv != "Critical"): return levels[rid] = lv current = None id_indent = 0 for line in md_text.splitlines(): m = id_line_re.match(line) if m: current = m.group(1) # 进入新风险记录 # id token 的列位置作为记录基准缩进:映射式 ` id:` 为行首缩进;列表式 # ` - id:` 需把列表标记宽度计入(否则其同缩进 level: 会被误判为嵌套) id_indent = line.find("id:") stripped = line.strip() if current and (not stripped or stripped.startswith("```") or stripped.startswith("#")): current = None # 记录边界:空行 / 围栏 / 标题(这些行也不会携带等级词) if CRITICAL_WORD_RE.search(line): lv = "Critical" elif HIGH_WORD_RE.search(line): lv = "High" else: lv = None rids = set(RISK_ID_RE.findall(line)) has_level_field = level_field_re.search(line) is not None has_subgate_level = (medium_word_re.search(line) is not None or low_word_re.search(line) is not None) if rids: for rid in rids: # 形态 1:行内同现 if lv: _raise(rid, lv) leveled.add(rid) if lv is None and (has_subgate_level or has_level_field): leveled.update(rids) # 行内有 Medium/Low 词或 level: 字段 → 有依据 elif current: if lv: # 形态 2:跨行记录内的等级行 _raise(current, lv) leveled.add(current) elif has_level_field and (len(line) - len(line.lstrip())) <= id_indent: leveled.add(current) # 记录自身缩进层的 level: 字段(Medium/Low 等) return levels, leveled def extract_risk_levels(md_text): """风险等级映射(单返回值兼容形式;提取缺口告警用 extract_risk_levels_detailed)。""" return extract_risk_levels_detailed(md_text)[0] def check_path_refs(items, repo_root, ctx, warnings): """抽查 items 中 "文件.ext:行号" 指涉在 repo_root 下真实存在;缺失按告警不按错误。 行号无法机械核对(代码会漂移),文件存在性是最便宜的反幻觉锚点: 抓"穿了 V2/V3 合规外衣、指向不存在文件的幻觉证据"这一最高频伪造形态。 """ seen = set() for item in items: m = REPO_FILE_LOC_RE.search(item) if not m: continue rel = m.group(1) key = f"{ctx}:{rel}" if key in seen: continue seen.add(key) if not (repo_root / rel).is_file(): warnings.append(f"{ctx}: 指涉文件不存在于 --repo-root 仓库: {rel}(核实是否幻觉证据或路径笔误)") def check_risk_coverage(cases, strategy_md_path, errors, warnings): """覆盖门禁:策略 Risk Map 中全部 Critical/High 风险必须被 ≥1 条用例的 risk_ref 反向覆盖。 risk-model.md §双向引用主从规则:机器消费一律走 case.risk_ref 反查—— 此前该末环无任何机械校验,Critical 风险零覆盖也能全绿过检。本函数补上这最后一道闸。 """ try: text = Path(strategy_md_path).read_text(encoding="utf-8").replace("\r\n", "\n") except OSError as e: errors.append(f"--strategy 文件不可读: {e}") return levels, leveled = extract_risk_levels_detailed(text) gaps = sorted(set(RISK_ID_RE.findall(text)) - leveled) if gaps: shown = ", ".join(gaps[:8]) + ("…" if len(gaps) > 8 else "") warnings.append( f"--strategy 风险编号 {shown} 无门禁等级依据(未提取到 Critical/High," "记录中也无 level 字段)——对应风险不参与风险覆盖门禁,请核对 Risk Map " "书写格式(支持 yaml 块 id 与 level 分行、表格行内同现两种形态)") known_ids = set(levels) covered = set() for case in cases: refs = as_list(case.get("risk_ref")) for r in refs: rid = r.strip() if RISK_ID_RE.fullmatch(rid): covered.add(rid) if rid and known_ids and rid not in known_ids: warnings.append(f"{case.get('id', '?')}: risk_ref='{rid}' 未在策略 Risk Map 中找到(疑似编号笔误)") gate = {rid for rid, lv in levels.items() if lv in ("Critical", "High")} for rid in sorted(gate - covered): errors.append(f"风险覆盖: {levels[rid]} 风险 {rid} 无任何用例通过 risk_ref 覆盖" "(证据链最后一环断裂——要么补用例挂 ref,要么经预算裁决降级并留痕)") # ---------- 模式二:测试策略类型域校验(V1–V5,配套 core/test-type-matrix.md) ---------- TYPE_AXES = ["performance", "security_business", "reliability", "concurrency", "compatibility", "accessibility", "visual", "i18n", "migration", "contract_integration"] FULL_MAX = 3 DEPTHS = {"full", "standard", "light"} # 轴 2(security_business)排除的合法依据形态:系统非 Web/API(命令行 / 桌面 / 离线库等)。 # 宽松匹配——告警级提示复核,不是门禁 NON_WEB_API_RE = re.compile( r"非\s*[-/·、\s]*[Ww]eb|非\s*[-/·、\s]*API|无\s*[-/·、\s]*[Ww]eb|无\s*[-/·、\s]*API" r"|不是\s*[Ww]eb|不是\s*API|命令行|CLI|桌面|离线|纯库|无网络|无界面") AXIS_LINE_RE = {axis: re.compile(rf"^[ \t]*{axis}:[ \t]*\{{(.+)\}}[ \t]*$", re.M) for axis in TYPE_AXES} FLOW_LINE_RE = re.compile(r"^[ \t]*(\w+):[ \t]*\{(.+)\}[ \t]*$", re.M) def parse_flow_map(inner): """解析单行 flow 映射 {k: v, ...};值保留原文,列表保留 [ ] 内文本。""" kv = {} for m in re.finditer(r"(\w+)\s*:\s*(\[[^\]]*\]|\"[^\"]*\"|'[^']*'|[^,{}]+)", inner): kv[m.group(1)] = m.group(2).strip() return kv def list_items(val): if not val or not val.startswith("["): return [] return [x.strip().strip("\"'") for x in val[1:-1].split(",") if x.strip()] def scalar(kv, key): """取标量值并剥引号。""" return kv.get(key, "").strip().strip("\"'").strip() def extract_scope_blocks(text): """合并所有含 functional_scope:/type_scope:/depth_budget: 的 yaml 块。 两域 scope 与 depth_budget(含 budget_review)可写进同一或不同代码块,均纳入检查; 注意 full_axes 声明须用行内数组写法(与 test-strategy SKILL.md 示例一致),多行展开式不校验。 """ parts = [b for b in re.findall(r"```yaml\n(.*?)```", text, re.S) if "type_scope:" in b or "functional_scope:" in b or "depth_budget:" in b] return "\n".join(parts) if parts else None def validate_strategy(path, repo_root=None): errors, warnings = [], [] # 统一换行(Windows 宿主产物的 CRLF 会让行锚定正则全部失配) text = path.read_text(encoding="utf-8").replace("\r\n", "\n") block = extract_scope_blocks(text) if block is None: errors.append("未找到含 type_scope:/functional_scope: 的 yaml 代码块——测试策略必须包含类型域十轴决策(V1)") return errors, warnings # Risk Map 等级抽取:供 V4-终态留痕判定哪些轴挂着 Critical 风险; # 同时支持 yaml 块 id/level 分行(risk-model.md §4 权威格式)与行内同现(表格/散文) risk_levels, leveled = extract_risk_levels_detailed(text) gaps = sorted(set(RISK_ID_RE.findall(text)) - leveled) if gaps: shown = ", ".join(gaps[:8]) + ("…" if len(gaps) > 8 else "") warnings.append( f"风险编号 {shown} 无门禁等级依据(未提取到 Critical/High,记录中也无 " "level 字段)——对应风险不参与 V4-终态留痕与风险覆盖门禁,请核对 " "Risk Map 书写格式(支持 yaml 块 id 与 level 分行、表格行内同现两种形态)") def has_mark(items, marks): return any(mk in s for s in items for mk in marks) for axis in TYPE_AXES: matches = list(AXIS_LINE_RE[axis].finditer(block)) if not matches: errors.append(f"V1 轴缺失或非单行 flow 风格: {axis}" f"(应形如 {axis}: {{ decision: include, depth: standard, signals: [...] }})") continue if len(matches) > 1: errors.append(f"V1 {axis}: 出现 {len(matches)} 次——每轴只允许一行决策,重复轴请合并") m = matches[0] kv = parse_flow_map(m.group(1)) decision = kv.get("decision", "") if decision not in ("include", "exclude", "handoff"): errors.append(f"V1 {axis}: decision='{decision or '(缺)'}' 不在 include/exclude/handoff") continue if decision == "include": if not (list_items(kv.get("signals")) or list_items(kv.get("risk_refs"))): errors.append(f"V2 {axis}: include 但 signals / risk_refs 均为空(无证据纳入 = 过测)") elif decision == "exclude": rationale = scalar(kv, "rationale") if not rationale: errors.append(f"V3 {axis}: exclude 缺 rationale(无理由排除 = 漏测)") elif axis == "security_business" and not NON_WEB_API_RE.search(rationale): # 硬默认轴(矩阵 §4:Web/API 系统一律 standard,无排除出口)—— # 脚本无法判定系统形态,故仅告警:排除仅在非 Web/API 系统成立, # rationale 未声明该依据时提示人工复核 warnings.append( f"V3 {axis}: exclude 但 rationale 未声明非 Web/API 依据——" "该轴为硬默认轴(Web/API 系统无排除出口),请复核排除是否成立") scanned = list_items(kv.get("scanned")) if not scanned: errors.append(f"V3 {axis}: exclude 缺 scanned 清单(G 级 + S 级双记录)") else: if not has_mark(scanned, ("(G)", "G级", "G 级", "G:")): errors.append(f"V3 {axis}: scanned 无 G 级扫描记录(G+S 双确认,缺 G 级 = 扫描未执行)") if "无代码" not in rationale and not has_mark(scanned, ("(S)", "S级", "S 级", "S:")): errors.append(f"V3 {axis}: scanned 无 S 级复核记录(exclude 须 G+S 双确认," "防脚本盲区制度化;无代码仓库场景须在 rationale 注明)") else: # handoff if not scalar(kv, "executor"): errors.append(f"V5 {axis}: handoff 缺 executor") if not scalar(kv, "handoff_ref"): errors.append(f"V5 {axis}: handoff 缺 handoff_ref(移交包文件,防移交即消失)") if scalar(kv, "execution_status") == "blocked" and not scalar(kv, "todo"): errors.append(f"V5 {axis}: execution_status=blocked 缺 todo(向谁索取什么)") executor = scalar(kv, "executor") if decision == "include" and executor and executor != "agent" and not scalar(kv, "handoff_ref"): errors.append(f"V5 {axis}: include + 外部执行器({executor}) 缺 handoff_ref") # V4:depth 枚举 + full 档逐行统计(functional_scope + type_scope 两域合并) full_axes = [] critical_low_axes = [] # include 且挂 Critical 风险但 depth<full 的轴名——终态留痕候选 seen_names = set() for m in FLOW_LINE_RE.finditer(block): name, inner = m.group(1), m.group(2) if name in seen_names: continue # 重复轴已由 V1 报错;统计以首处为准 seen_names.add(name) kv = parse_flow_map(inner) depth = scalar(kv, "depth") decision = kv.get("decision", "") # depth 缺省警示:类型域 include 轴必须显式声明档位,否则绕过 full_axes/预算审计链; # exclude/handoff 轴按 schema 约定本就不写 depth("不测"是范围决策),不告警防误伤 if name in TYPE_AXES and not depth and decision not in ("exclude", "handoff"): warnings.append( f"V4 {name}: depth 缺省——档位未声明则不参与 full_axes/预算审计," "请显式写入 full/standard/light('不测'应表达为 exclude/handoff 并挂理由)") # 指涉真实性抽查:signals / scanned 中 "文件.ext:行号" 必须真实存在于仓库(告警级, # 抓"格式合规的幻觉证据",见 --repo-root 参数说明) if repo_root is not None: check_path_refs(list_items(kv.get("signals")) + list_items(kv.get("scanned")), repo_root, name, warnings) # V4-终态留痕候选:挂 Critical 风险却未给足深度的降档裁量,必须在 budget_review 留痕 rids = [r.strip() for r in list_items(kv.get("risk_refs"))] if (decision == "include" and depth and depth != "full" and any(risk_levels.get(r) == "Critical" for r in rids)): critical_low_axes.append(name) if depth and depth not in DEPTHS: errors.append(f"V4 {name}: depth='{depth}' 不在 full/standard/light('不测'是范围决策:" "功能域 include: false,类型域 decision: exclude/handoff,均须挂理由)") if depth == "full": full_axes.append(name) if not list_items(kv.get("risk_refs")): errors.append(f"V4 {name}: depth=full 但 risk_refs 为空(须挂风险编号作升档依据)") m_budget = re.search(r"full_axes:\s*\[([^\]]*)\]", block) if m_budget: declared = [x.strip() for x in m_budget.group(1).split(",") if x.strip()] if sorted(declared) != sorted(full_axes): errors.append(f"V4 depth_budget.full_axes {declared} 与实际 full 轴 {full_axes} 不一致") elif full_axes: # 实际存在 full 轴却检不出行内数组声明:或漏写、或用了多行展开式(后者不受支持), # 两种情况都令一致性核对失效——警示级提示人工补声明 warnings.append( f"V4 未检出 depth_budget.full_axes 行内数组声明(实际 full 轴 {full_axes})" "——缺声明或多行展开式(不受支持),无法核对一致性") if len(full_axes) > FULL_MAX and not re.search(r"^[ \t]*budget_review:", block, re.M): errors.append(f"V4 full 轴 {len(full_axes)} 个 > {FULL_MAX}(两域合并):" "须触发预算裁决检查点并记录 budget_review(R6 > R1)") # V4-终态留痕:堵住"先超限触发裁决、再裁剪回 ≤3 后无迹可查"的逃逸窗口—— # 每条 Critical 降档裁量都须在 budget_review 文本中留下轴名,不许无声消失 if critical_low_axes: m_br = re.search(r"(?ms)^[ \t]*budget_review:[ \t]*(.*?)(?=^[ \t]*\w+:|\Z)", block) br_text = m_br.group(1) if m_br else "" for ax in critical_low_axes: if ax not in br_text: errors.append( f"V4-终态 {ax}: include 且挂 Critical 风险但 depth 未达 full," " 且 budget_review 中无该轴的降档依据(深度降档属重大裁量,须留一行依据)") return errors, warnings def build_arg_parser(): """构造 CLI 参数解析器:位置文件参数个数区分两模式,可选增强参数可叠加。""" parser = argparse.ArgumentParser( description="Test Case Schema 校验器(模式一)+ 测试策略类型域校验(模式二)", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__) parser.add_argument("files", nargs="+", metavar="文件", help="1 个 = 测试策略.md(模式二);2 个 = markmap + schema.yaml(模式一)") parser.add_argument("--strategy", metavar="PATH", default=None, help="仅模式一:风险覆盖门禁——策略 Risk Map 中的 Critical/High 风险" "必须被至少一条用例的 risk_ref 反向覆盖") parser.add_argument("--repo-root", metavar="DIR", default=None, dest="repo_root", help="指涉真实性抽查:signals / code_refs 中 '文件.ext:行号' 形式的" "引用须在该目录下真实存在(缺失告警)") return parser def main(argv=None): """CLI 入口:按位置参数个数分发模式一/二,并叠加覆盖门禁与指涉抽查两项增强校验。""" args = build_arg_parser().parse_args(argv) paths = [Path(p) for p in args.files] repo_root = Path(args.repo_root) if args.repo_root else None strategy_md = Path(args.strategy) if args.strategy else None # 参数前置体检:目录/文件存在性在进任何业务校验前拦截 if repo_root is not None and not repo_root.is_dir(): print(f"错误: --repo-root 不是存在的目录: {repo_root}") return 1 if len(paths) > 2: print(build_arg_parser().format_usage()) print("错误: 文件参数最多 2 个(markmap + schema.yaml,或仅一个测试策略.md)") return 1 for p in paths: if not p.is_file(): print(f"错误: 文件不存在或不是普通文件 {p}") return 1 if strategy_md is not None and len(paths) != 2: print("提示: --strategy 仅模式一(两个文件参数)生效,本次调用已忽略") strategy_md = None # ---------- 模式二:测试策略类型域校验(单文件参数) ---------- if len(paths) == 1: p = paths[0] errors, warnings = validate_strategy(p, repo_root=repo_root) for e in errors: print(f" ✗ {e}") for w in warnings: print(f" ⚠ {w}") verdict = "✅ 类型域决策校验通过(V1–V5 + 终态留痕)" if not errors else f"✗ {len(errors)} 个错误" print(f"{verdict}: {p.name}") return 1 if errors else 0 # ---------- 模式一:用例 Schema 校验(双文件参数) ---------- md_path, yaml_path = paths errors, warnings = [], [] for _p, _label in ((yaml_path, "schema"), (md_path, "markmap")): try: _p.read_text(encoding="utf-8") except UnicodeDecodeError: print(f"错误: {_label} 文件非 UTF-8 编码({_p})。" "请以 UTF-8 重新保存后重试(Windows 记事本默认 ANSI/GBK)") return 1 yaml_text = yaml_path.read_text(encoding="utf-8") md_text = md_path.read_text(encoding="utf-8") cases = [] shared_pres = {} # {前置原文: 模块名}:顶层 modules 表的共享前置,供去冗余复核 parsed_ok = False # 仅在 PyYAML 真正完成解析时置位(降级 lint 不算,防止误报零用例) try: import yaml # type: ignore docs = [d for d in yaml.safe_load_all(yaml_text) if d is not None] for doc in docs: walk_cases(doc, cases) shared_pres.update(collect_shared_preconditions(doc)) parsed_ok = True except ImportError: warnings.append("未安装 PyYAML,降级为基础 lint(引号配对 / 裸引号),建议 pip install pyyaml") lint_yaml_basic(yaml_text, errors) except Exception as e: # yaml.YAMLError 等 errors.append(f"YAML 解析失败(检查转义纪律,见 core/schema-extraction.md): {e}") # 零用例骗绿防线:YAML 合法但一条 TC 都没收集到(顶层 key 拼错/结构漂移的典型形态), # 直接判失败——格式合法的空产物是最常见的失败形态,不能让它全绿通过 if parsed_ok and not cases: errors.append("schema 中未发现任何 TC 用例(检查顶层 key 是否为 cases、" "结构是否符合 core/schema-extraction.md 的三层形态:strategy_ref / modules / cases)") for case in cases: check_case(case, errors, warnings) # 共享前置去冗余复核(case-format §5):与模块级重复声明仅告警 cid = str(case.get("id", "?")) for pre in as_list(case.get("preconditions")): if pre in shared_pres: warnings.append( f"{cid}: 前置 '{pre}' 与模块级共享前置重复" f"(module={shared_pres[pre]}),应只写该条特有部分") # 代码模式弱断言(schema-extraction「模式相关必填」):markmap 顶部带测准声明即为 # 代码模式,code_refs 与 evidence 三件套必填——字段留有"注明实现形态"的豁免出口, # 无法机械判定豁免是否成立,故为告警级,不做门禁 if "测准声明" in md_text: for case in cases: cid = str(case.get("id", "?")) refs = case.get("code_refs") refs_items = refs if isinstance(refs, list) else ([refs] if refs else []) if not refs_items: warnings.append( f"{cid}: 代码模式但 code_refs 为空——无代码位置即没有被审查资格" "(确无一比一对应实现时注明实现形态,见 core/schema-extraction.md)") ev = case.get("evidence") if not isinstance(ev, dict) or not all(ev.get(k) for k in ("level", "source", "confidence")): warnings.append( f"{cid}: 代码模式但 evidence 三件套(level/source/confidence)不全" "(代码模式必填,见 core/schema-extraction.md)") if cases: yaml_ids = [c["id"] for c in cases] md_ids = extract_markmap_tcs(md_text) orphans = sorted(set(yaml_ids) - set(md_ids)) missing = sorted(set(md_ids) - set(yaml_ids)) for tc in orphans: errors.append(f"{tc}: schema 中存在但 markmap 无此用例(孤儿条目)") for tc in missing: warnings.append(f"{tc}: markmap 中存在但 schema 未抽取(遗漏,确认是否应抽取)") if len(yaml_ids) != len(set(yaml_ids)): dupes = sorted({t for t in yaml_ids if yaml_ids.count(t) > 1}) errors.append(f"schema 中 id 重复: {', '.join(dupes)}") # 增强校验一:--strategy 风险覆盖门禁(证据链最后一环) if strategy_md is not None: check_risk_coverage(cases, strategy_md, errors, warnings) # 增强校验二:--repo-root 指涉抽查(code_refs 反幻觉锚点) if repo_root is not None: for case in cases: check_path_refs(as_list(case.get("code_refs")), repo_root, str(case.get("id", "?")), warnings) for e in errors: print(f" ✗ {e}") for w in warnings: print(f" ⚠ {w}") n_ok = len(cases) verdict = "✅ 校验通过" if not errors else f"✗ {len(errors)} 个错误" print(f"{verdict}(用例 {n_ok} 条,错误 {len(errors)},告警 {len(warnings)}): {yaml_path.name}") return 1 if errors else 0 if __name__ == "__main__": sys.exit(main())
-
-
case-format.md 10.1 KB
# 用例格式规范(case-format) > 框架级用例格式硬约束全集(`test-case-writing` 编写、`test-case-review` 修订、`bug-analysis` 建议新增用例时统一遵守),编写与定稿自检时**逐条执行本文件**,不是可选参考。从零拼装用例文件的起点模板(含详细格式范例)由 test-case-writing skill 的 templates 目录提供。 ## 1. 文件头部导读区(硬性产出,任何模式) 用例正文(第一个模块)之前必须有导读区,标准是**零上下文新人**(新入职、没读过需求文档、没人讲解、只拿到这一份文件)能直接开工。四件套: 1. **功能简介 + 角色表**:一两句话说清这是什么功能;每个角色一行「角色 → 用它做什么」 2. **环境与账号表**:后台入口地址、App/客户端获取方式、每个角色的测试账号。信息未知时**不省略**——列占位行标注「TODO:向 {谁} 索取」,让读者开工前知道要找谁拿什么 3. **术语表**:正文用到的内部系统名(如风控系统代号)、英文字段名、状态代号,逐条「术语 → 中文名 → 一句话解释」。判定标准:正文出现的每个非通用词,术语表可查 4. **图例**:P0/P1/P2 含义与未标注时的默认级别、`SMOKE-` 前缀、`[需Mock]` 等标签含义 ## 2. 正文零代码内部(硬约束,最重要) 用例正文是给**测试工程师看和执行**的,必须用业务语言。正文禁出现: - 代码位置:`文件:行`(如 `set.go:35`、`create.tsx:42`) - 代码符号:SDK/库的类与函数(如 `srchbase.StringColumn`、`PlatformCli`、`json.Marshal`) - 错误码与控制流:`resp.Code != 0`、`SetAbort`、`return nil`、`fmt.Errorf` - 实现术语:`Qualifiers 下推`、`nil 守卫`、`OptionType 分支` 一律改业务语言:`文件:行` → 不出现(落附录);`resp.Code != 0` → "服务端返回业务错误";`SetAbort` → "任务进错误文件";`Qualifiers 下推` → "只返回请求的字段"。 **允许保留**(这些是测试要用的业务/配置事实,不是代码内部):功能/接口/stage 名(如 `hbase.set`)、配置项 key(如 `fields`)、业务字段名(如 `lr_label_top`)、业务枚举值(如 `DataType=图片`)、可观察的日志原文(作为判定标准时引用,如错误信息含 `is required`)。 > 判定标准:随机挑一条用例,让一个**不读代码**的测试工程师复述"我要验证什么、给什么数据、怎么算通过"。复述不出 → 违反本约束,改写。 ## 3. 用例编号(硬性,供交叉引用) - 每条用例唯一编号 `TC-{模块号}-{序号}`(如 `TC-02-03` = 模块 2 第 3 条),写在名称前:`- **TC-02-03 {用例名称}** [P1]` - 模块号对应正文一级模块编号(`## 2.` 模块的用例为 TC-02-xx) - P0 冒烟用例在名称后追加冒烟序号标注(如 `(SMOKE-1)`),便于快速抽取冒烟集 - 附录交叉引用(代码证据清单、风险点 Dn 覆盖用例、改动文件映射)与增量更新的变更标记,一律引用 TC 编号 ## 4. 详细格式(默认) **有 UI 功能**用四段式(优先级标注在名称行): ```markdown - **TC-{模块号}-{序号} {用例名称}** [P0/P1/P2] - 前置条件: {该条特有的前置;无特有时可省略此行} - 操作步骤: 1. 进入{页面} 2. 填写{字段} 3. 点击{按钮} - 预期结果: {页面上能观察到的明确现象} ``` **无 UI 后端功能**用协作五段式(优先级同样标注在名称行,不单列字段): ```markdown - **TC-{模块号}-{序号} {用例名称}** [P0/P1/P2] - 前置条件: {已准备的数据,如测试编号/账号} - 操作步骤(请开发执行): 1. 用{功能名}处理{具体数据} 2. {触发方式} - 预期结果(请开发反馈): {明确唯一的可观察结果} - 验证方式: {开发查库/查日志的具体位置与反馈内容} ``` 只有用户明确要求紧凑格式、或信息密度优先(冒烟清单、快速审查)时,才使用紧凑格式: ```markdown - TC-{模块号}-{序号} 操作条件,预期结果 [P0/P1/P2] ``` > 代码模式下 `代码依据: 文件:行` **不写在每条用例里**;若需溯源,在附录「代码证据清单」统一列表(用例编号 ↔ `文件:行`)。 ## 5. 步骤与判定细则 - **前置条件去冗余**:同一子模块下多数用例共享的前置(如"已登录管理员""已创建项目X"),在该子模块标题下方用引用块统一声明一次(`> 前置:...`);单条用例的「前置条件」行只写该条额外需要的条件,没有额外前置时可省略该行 - **嵌入具体数据**:操作步骤里用具体的测试数据(真实编号、字段名、字段值),不要写占位符 `{xxx}`,让测试能直接照着执行或整包发给开发 - **页面可达性**:每个被测页面/入口在文件中首次出现时,写清从哪里到达(如「后台 → 营销中台 → 券工场 → 活动列表」)。入口在输入源中未说明时,不得只写「打开 XX 页」蒙混——列入澄清问题,并在导读区环境表标注「TODO:入口待确认」 - **异步行为必含判定时限**:预期结果含「自动变为 / 稍后更新 / 异步生效」时,必须写明等待多久不发生即判失败(如「到达结束时间后 5 分钟内自动变为已结束,超过 5 分钟未变判失败」)。时限无依据时按输入源指标推算或列入澄清问题,不得留白让执行者自定 - **断言范围不超出验证强度**:用例名称的断言不得大于步骤实际能证明的范围——两个样本证不了「全局唯一」,名称应改为「不同活动的编号互不相同」;确需全局断言时改为协作用例(请开发查库确认约束) - **一条用例只测一个点**:预期结果明确唯一,不写"或 A 或 B"——应拆分为两条 ## 6. 可测试性标注 - `[需真机]` → 替代:模拟器 + Mock 数据,标注为 P2 - `[需Mock]` → 替代:写 Mock 脚本并记录在附录中 - `[需专业环境]` → 替代:改为单元测试或标记"需专项测试" - 类型域轴标签:`[并发]` / `[可靠]` / `[安全]` / `[兼容]` / `[迁移]` / `[集成]` / `[国际化]`——来自测试策略 type_scope 的 include 轴(消费方式映射见 `test-type-matrix.md` 第 12 节),与 Schema 的 type 字段配套 带标注的用例,附录「可测试性说明」必须落到**可行动**:具体平台/工具名与操作入口,未知则写「TODO:向 {谁} 确认 {什么}」。只写「需 Mock 平台配置」而不说平台是什么、找谁,等于没写。 ## 7. Markmap 层级规范 ```markdown # 项目名 测试用例 ## 1. 模块名 ### 1.1 子模块名 #### 1.1.1 细分类别 - TC-01-01 具体测试点 [P0] ``` 附录内容(数据模型、参数定义等)放在文件末尾,不影响思维导图渲染。 ## 8. 测准声明(代码模式必加,文件顶部) 代码模式下,用例文件顶部必须加测准声明: > **测准声明**:本文件以 `{仓库} {分支}` 实际实现为唯一功能基线;需求/设计文档降为背景对照(偏离见附录)。代码级证据(`文件:行`、缺陷记录、接口对照)统一放文件末尾附录,**不写进用例正文**(见「正文零代码内部」)。 ## 9. 附录区规范(代码模式必含,与正文 `---` 物理隔离) 代码模式的用例文件,除功能用例外,必须含以下产出——**全部放在文件末尾的附录区**,附录顶部标注"开发技术核查清单(非测试执行项)"。Cx/Dn 记录的 evidence 字段格式见 `evidence.md`,Dn 评级见 `risk-model.md`: - **附录:缺陷记录 Cx**:编号(C1、C2…)+ 现象描述 + `文件:行` 证据 + 证据等级(E0–E4)+ 置信度 + 处置(主线验证 / 专项验证 / 待实测确认 / 已证伪 / 后端范围) - **附录:高风险点 D1-Dn**:编号 + 风险描述 + 等级(Critical/High/Medium/Low,Impact × Likelihood 评分)+ `文件:行` 证据 + 覆盖用例编号 + 通过判据 - **附录:代码证据清单**(可选):用例编号 ↔ `文件:行` 对照,供开发溯源(Schema 的 `code_refs` 由此抽取) - **附录:文档偏离对照**:表格列「设计点 / 文档描述 / 实际实现 / 偏离类型」 - **附录:改动文件映射**:表格列「改动文件 / 影响模块 / 回归用例 / 分类(🆕 新建 · ✏️ 修改 · 🗑️ 删除 · 💀 死代码——与阶段一「改动盘点」四类一致,`regression-testing` 沿用同一枚举)」 - **附录:可测试性说明**(任意模式,存在带标注用例时):按 TC 编号逐条列出,替代方案落到可行动 > 这些代码级内容是给**开发/评审**核查用的,测试工程师不执行、也不应被其干扰。Cx/Dn 中映射的"覆盖用例"指向正文中的业务语言用例。**正文与附录物理隔离是本规范的核心要求**——不要把 Cx/Dn/`文件:行` 当作正文的一个模块穿插在功能用例之间。 > 纯文档模式(无代码)不产出以上内容,降级时已提示用户准确性受限。 ## 10. 审查记录与变更报告格式 审查记录(追加在文件末尾,附录之后)。**适用范围**:本节模板供 `test-case-writing` 写时自审(阶段四)使用;`test-case-review` 独立审查的审查记录以其 SKILL.md 模板为唯一来源(含基准可信度 / 缺失 / 冗余 / 错误 / 高风险未覆盖字段,标题带「test-case-review {日期}」落款以便与写时自审节区分)。两节各自追加、不互相覆写: ```markdown ## 审查记录 - 审查前:XX 条用例,XX 个模块 - 审查后:XX 条用例,XX 个模块 - 新增:XX 条({简述补充了哪些场景}) - 调整:XX 条({简述调整内容}) - P0/P1/P2 分布:XX/XX/XX ``` 变更报告(增量更新时追加在审查记录之后): ```markdown ## 测试用例变更报告 — {日期} - 变更来源:{需求文档版本 / Bug 单号} - 影响模块:{模块列表} - 新增用例:XX 条 | 修改用例:XX 条 | 废弃用例:XX 条 ``` -
clarify-pattern.md 3.8 KB
# 统一澄清与确认格式(clarify-pattern) > 全框架唯一的"向用户提问"格式。`requirement-analysis`(需求澄清)、`test-case-writing`(阶段二澄清)、`automated-e2e-testing` / `api-testing`(执行中确认)、`exploratory-testing`(异常定性)统一使用,用户看到的问题卡片跨 skill 长一个样。 ## 核心规则 **不确定就问,宁可多问不要瞎猜;问了就等答复,不自行假设。** 用户在澄清环节的明确答复具有最终裁决力(语义见 `evidence.md` 第 2 节裁决规则),必须落盘到产物(需求模型 `open_questions` / 澄清记录 / Bug 定性标注),后续阶段不得推翻。 ## 统一格式 ```markdown 🔍 {场景}:{一句话问题概述} - **问题来源**: {文档名+章节 / `文件:行` / 页面或接口 / 用例 TC 编号} - **具体问题**: {一句话提问,不嵌套多个问题} - **影响**: {影响哪些模块 / 用例 / 判断} - **建议选项**: A) ... B) ...(如有依据才给;无依据则留空,不要编选项) ``` - **场景** 取值:`需求澄清`(规则/边界/状态/权限模糊)、`执行确认`(定位失败/路径不通/预期歧义/环境索取)、`Bug 定性`("预期行为还是缺陷?") - 多个问题逐条独立成卡,不合并成一段叙述;单条简单问题可用紧凑单行:`🔍 需求澄清:**来源** {文档/位置}|**问题** ...|**建议** ...` ## 何时必须问(命中即问) 1. 功能/规则/流程描述模糊,或边界条件(最值、上限、超时)未定义 2. 状态流转缺失转换路径、角色权限未说明 3. 文档间 / 文档与代码不一致(列两边原文与位置,问"以哪个为准") 4. 异步/延迟行为无判定时限 5. 元素定位失败、操作入口不存在、预期与实际二义(执行类) 6. 发现异常行为,无法自判"预期还是缺陷"(定性类) ## 需求歧义九类漏网模式(扫描矩阵;本文件为单一权威源) > 需求类澄清(requirement-analysis / test-case-writing 阶段二)在「何时必须问」之上, > **逐类过筛材料**,命中的必须进澄清清单。评测实测:无此矩阵时模型对隐蔽歧义的命中率反低于裸模型; > 补入矩阵后澄清维度命中率 25%→50%。 | # | 模式 | 扫描问题 | |---|------|---------| | A | 时间口径 | 每个"X 天/小时/年/每天 N 次":起算点?自然单位还是滚动窗口?含否当日/时区? | | B | 数值边界含否 | 每个"1–20/N 以内/不超过":端点含否?非整数/取整方向? | | C | 规则冲突与叠加 | 同一对象多处规则(正文 vs 活动页 vs FAQ):替代还是叠加?冲突以谁为准? | | D | 术语一致性 | 同一概念多个名字("商城/兑换中心"类)?是否同一入口/模块? | | E | 异步时限 | 每个异步/延迟/自动行为:多久不发生判失败?无时限即不可测,必须问 | | F | 部分场景 | 退款/部分支付/部分失败:按比例还是全额?负数/欠额如何处理? | | G | 并发与顺序 | 并发到达以谁为准?用户操作与定时任务/自动流程的竞态? | | H | 未定义的判定口径 | "异常账号""高风险"等判定标准;历史数据保留期限与可查范围 | | I | NFR 指标空缺 | 性能/容量/可靠性/兼容性只有定性主张无指标:吞吐、并发数、响应时间阈值各是多少?兼容基线(系统/版本/分辨率/浏览器)?SLA 与其测量方法?——无指标即不可测:要么问到数值,要么用户显式裁定"本版不验证"进 scope 非目标 | ## 何时不问 - 代码可静态裁决的事实(以代码为准,偏离记录后统一提交,不逐条打断) - 产物内部组织、格式、命名等不影响正确性的自由度 -
coverage.md 12.2 KB
# 覆盖度检查表(coverage) > 编写完测试用例后,逐维度检查。分为**核心维度**(所有项目必检)和**扩展维度**(按项目类型选取)。 > 对每个维度问一个核心问题——**"这个维度上是否有遗漏?"**,而非"这个维度是否覆盖了?" > > 本文件同时承载**代码审查发现模式清单**(第 7.1 节),供阶段一代码探索与阶段四覆盖审查共用。`test-strategy` 制定范围、`test-case-review` 独立审查时同样引用本清单。 > 类型性维度(11 / 13–16 / 18 / 19)的**纳排与深度决策已上收**至 `test-type-matrix.md`(test-strategy 产出 type_scope)——本文件的类型性维度仅作用例设计提示,不是范围决策来源。 > > **执行强度按消费方分流**(防"全部检查项"被误读为 19 维机械全检): > - **编写流程**(`test-case-writing`):核心维度**部分内联**为其阶段三交付核对——维度核对仅三项硬核对(主流程 / 时间双侧 / 负向底数);其余核心维度不设编写流程内强制核对(设计期靠方法选择覆盖,本文件仅为按需参考),由独立审查全检兜底;阶段四仅固定抽查 5 项(主流程复查 / 单一职责 / FAQ 非目标 / 二阶交叉 / 跨模块重复),不从本清单取点; > - **独立审查**(`test-case-review`):核心 7 维逐项全检 + 横切可执行性,扩展维度按下方「维度选取速查」按项目类型选取; > - 任何场景都不做扩展维度的机械全检。 --- ## 核心维度(7 个) > 1-6 所有项目必检;**7 代码审查发现**为代码模式必检(无代码仓库则跳过该维度)。 ### 1. 功能主流程 每个功能点的正向路径是否走通?每个功能选项是否有对应用例? **典型遗漏**:只测了"创建成功",没测"创建后数据是否正确持久化"。 ### 2. 输入校验 必填缺失、格式非法、边界值(空/最大/最小/零值)、多值分隔符,是否都有对应的拦截用例?**每条校验规则是否在所有写入路径(创建/编辑/重新提交/导入)上都验证过,而不是只挂在创建?**(方法见 `methods/boundary.md`) **典型遗漏**:创建时验了"门槛须大于面额",驳回后编辑重提时没验——编辑路径绕过校验是真实系统的高发 bug。 ### 3. 逆向操作与生命周期 创建→删除、分配→释放、导入→撤销、修改→恢复,每对操作是否都有验证?删除/释放后关联数据是否级联清理?**每种失败/拦截场景之后,重试恢复是否有用例(失败 → 再试 → 成功)?** **典型遗漏**:只测了"领取成功",没测"领取后释放任务,记录是否恢复";测了"风控拦截提示",没测"拦截过的用户之后正常领取是否放行"。 ### 4. 状态流转 完整状态机的每一条边(A→B)是否覆盖?非法转换是否拒绝?并发状态变更是否有竞态条件?(方法见 `methods/state-machine.md`) **典型遗漏**:只测了"待领取→标注中→已提交",没有覆盖完整状态机。 ### 5. 数据一致性 级联操作后关联数据是否一致?重复操作是否幂等?数据清理/归档后是否有残留?**名称/编号等标识字段重复提交时的唯一性行为是否验证?** **典型遗漏**:只测了正常写入,没有测试重复导入时的幂等行为;没测"创建两个同名活动"系统是允许还是拦截。 ### 6. 文档隐含需求 FAQ 每个问答是否转化了至少 1 条用例?"非目标"声明的限制是否有验证用例?注释、脚注中的约束条件是否被遗漏? **典型遗漏**:FAQ 明确"复审队列就是普通队列",但没有设计用例验证。 ### 7. 代码审查发现(代码模式必检;无代码则跳过) 全面代码审查发现的每个潜在 bug 是否都有验证用例?高风险点 D1-Dn 是否每条都有回归用例?死代码是否确认可忽略(不写用例)?文档偏离是否已确认(记入附录)?证伪项是否已核实(避免伪用例)?**每个发现的验证用例本身是否用业务语言编写、代码证据(`文件:行`)是否隔离到了附录(不污染正文)?** **典型遗漏**:只测功能,未验证代码中的 null 未兜底 / 边界未防护(NaN/负数/空数组)/ 状态死锁等潜在 bug;或把已证伪的风险点(无下游消费)当成真 bug 写了用例;或把 `文件:行`/SDK 符号直接写进用例正文,导致测试工程师看不懂。 #### 7.1 代码审查发现模式清单(阶段一代码探索逐项查找) 每条发现附 `文件:行` 证据并标注确信度,evidence 格式见 `evidence.md`: | # | 模式 | 查找要点 | |---|------|---------| | 1 | null/undefined 未兜底 | `.map`/`.length`/`.width` 直接作用于可能为空的值 | | 2 | 可选链断裂 | 链式取值中间层可能为空,后半段未防护 | | 3 | 边界未防护 | NaN / 负数 / 空数组 / 超大值 / 0 直接进入计算 | | 4 | 状态死锁 | 改了状态却被门控条件拦截,流转不出去 | | 5 | 副作用泄漏 | 全局 state / 缓存修改后未 cleanup | | 6 | 并发竞态 | 检查-后-使用(TOCTOU)、并发写覆盖 | | 7 | 硬编码 | project_id / 环境 / 魔法数固定写死 | | 8 | 权限遗漏 | 只在入口鉴权,内部路径/接口未二次校验 | | 9 | 异步未 await | Promise 未等待、错误未捕获 | | 10 | 错误静默吞掉 | 空 catch / 只打日志不反馈用户 | | 11 | 文档 vs 实现不一致 | 字段名/类型、流程顺序、字段是否真提交、默认值 | | 12 | 死代码/死声明 | 未挂载路由的模块、声明未解构的 props、未调用的 API | | 13 | 证伪项 | 看似高风险实则无下游消费的字段/状态(核实后降级,避免伪用例) | ### 横切必检:可读性与可执行性(所有项目,定稿前必做) 以**零上下文新人**(没读过需求文档、没人讲解)为标准,逐项执行 `executability.md` 的定稿自检清单:文件头部有无导读四件套?每条用例**看得懂**(正文零代码内部、内部系统名与英文字段名可在术语表查到)?能否**开工执行**(页面入口写明、前置状态有制造方法、异步预期有判定时限、Mock 依赖写明找谁)?独立执行(有 UI)或协作执行(无 UI,标注请开发执行/反馈)?嵌入的是具体数据还是占位符? **典型遗漏**:用例堆满 `文件:行`/SDK 符号/错误码常量;"风神""coupon_batch_no"裸用不解释;几十条用例第一步"打开活动页"但入口全文未说明;"自动变为已结束"没写等多久算失败;环境地址与测试账号只字未提,新人第一步登录就卡住。 --- ## 扩展维度(按项目类型选取) ### 8. 技术实现细节(有设计文档的项目) 数据库约束是否生效?缓存加载时机和一致性?异步任务状态追踪和失败恢复?消息格式和向后兼容? **典型遗漏**:设计方案提到"一次性加载缓存",但没有测试导入过程中缓存数据变化的场景。 ### 9. 权限与角色(多角色系统) 普通用户、管理员、无权限用户的操作是否验证?角色变更后权限是否即时生效?(方法见 `methods/permission.md`) **典型遗漏**:FAQ 明确"管理员可浏览全部",但没有设计管理员在绑定模式下的测试用例。 ### 10. 错误处理与反馈(所有项目推荐) 错误信息是否清晰且包含定位信息?部分失败时成功部分是否保留?错误恢复后系统状态是否一致? **典型遗漏**:只测了"报错",没有验证错误信息的具体格式和可操作性。 ### 11. 并发与性能(有性能要求的项目) > 类型纳排与深度决策以 `test-type-matrix.md` 为准(轴 1 性能效率 / 轴 4 并发一致性)。 多用户并发操作同一资源?批量操作性能基线?查询延迟上限?高并发下无重复无遗漏无死锁? **典型遗漏**:设计方案明确给出"P99 < 50ms"的性能指标,但没有转化为具体的性能测试用例。 ### 12. 链式/复杂流程(有多阶段操作的项目) 多阶段端到端验证?跨阶段数据传递完整性?多轮操作(>2 轮)累积正确性?中断后恢复/重试? **典型遗漏**:只测到第 2 轮复审,没有验证第 3 轮、第 4 轮时历史数据是否正确累积。 ### 13. 安全性(Web/API 项目必选) > 类型纳排与深度决策以 `test-type-matrix.md` 为准(轴 2 业务安全;越权功能正确性归功能域 permission)。 未认证/越权访问拦截?注入攻击防护?多租户数据隔离?敏感数据保护?接口限流? **典型遗漏**:只测了功能正确性,没有测试用户 A 能否访问用户 B 的数据。 ### 14. 兼容性(有前端的项目必选) > 类型纳排与深度决策以 `test-type-matrix.md` 为准(轴 5 兼容性)。 目标浏览器兼容?移动端适配?不同屏幕尺寸/分辨率? **典型遗漏**:Chrome 上正常,Safari 上日期选择器无法弹出。 ### 15. 国际化(多语言/海外项目) > 类型纳排与深度决策以 `test-type-matrix.md` 为准(轴 8 国际化)。 语言切换、时区处理、货币格式、RTL 布局、文案溢出/截断? **典型遗漏**:时间戳在 UTC+8 正确,在 UTC-5 显示错误。 ### 16. 网络与连接(移动端/实时系统) > 类型纳排与深度决策以 `test-type-matrix.md` 为准(轴 3 可靠性)。 弱网环境、断网重连、请求超时、离线模式? **典型遗漏**:正常网络下提交成功,弱网下用户点击两次导致重复提交。 ### 17. 通知/消息(有通知功能的项目) 消息发送正确性、失败重试、去重、模板变量渲染? **典型遗漏**:只测了"消息发出",没有测试消息内容中变量替换是否正确。 ### 18. 第三方集成(调用外部服务) > 类型纳排与深度决策以 `test-type-matrix.md` 为准(轴 10 契约与集成)。 正常/超时/失败/降级?回调/Webhook 处理?异常数据容错? **典型遗漏**:只测了第三方 API 正常返回,没有测试超时后的重试和降级。 ### 19. 数据迁移/升级(存量系统迭代) > 类型纳排与深度决策以 `test-type-matrix.md` 为准(轴 9 迁移与升级)。 Schema 变更后老数据兼容?迁移完整性?新旧版本共存?回滚后数据可用? **典型遗漏**:新版本上线后,历史数据的展示格式异常。 --- ## 维度选取速查 | 项目类型 | 核心维度 | 额外必选维度 | |---------|---------|------------| | Web 应用(前后端) | 1-7 | 8, 9, 10, 13, 14 | | 纯 API 服务 | 1-7 | 8, 10, 11, 13 | | 移动端应用 | 1-7 | 10, 14, 16 | | 多端项目 | 1-7 | 按端分别选取 + 跨端一致性检查 | > 编号说明:1-7 为核心维度,其中 **7 代码审查发现**为代码模式必检(无代码仓库跳过)。所有项目类型在有代码仓库时均必选维度 7。 --- ## 自查结果模板 > 此表供人工评审或团队复盘时填写。AI 执行按 test-case-writing 阶段四的**抽查口径**执行(逐维度全检已废除),抽查结果并入用例文件末尾的「审查记录」,**不单独产出此表**(SKILL.md 规定不产出中间文件);本清单作为抽查选点与按需加深的维度索引。 | 维度 | 适用 | 覆盖状态 | 缺失场景 | 补充用例编号 | |------|------|---------|---------|-------------| | 1. 功能主流程 | 核心 | | | | | 2. 输入校验 | 核心 | | | | | 3. 逆向操作与生命周期 | 核心 | | | | | 4. 状态流转 | 核心 | | | | | 5. 数据一致性 | 核心 | | | | | 6. 文档隐含需求 | 核心 | | | | | 7. 代码审查发现 | 核心(代码模式) | | | | | 横切:可读性与可执行性 | 核心(所有项目) | | | | | 8. 技术实现细节 | 扩展 | | | | | 9. 权限与角色 | 扩展 | | | | | 10. 错误处理与反馈 | 扩展 | | | | | 11. 并发与性能 | 扩展 | | | | | 12. 链式/复杂流程 | 扩展 | | | | | 13. 安全性 | 扩展 | | | | | 14. 兼容性 | 扩展 | | | | | 15. 国际化 | 扩展 | | | | | 16. 网络与连接 | 扩展 | | | | | 17. 通知/消息 | 扩展 | | | | | 18. 第三方集成 | 扩展 | | | | | 19. 数据迁移/升级 | 扩展 | | | | -
evidence.md 4.3 KB
# 统一证据体系(Evidence System) > 全框架唯一的证据规范,所有 Skill 统一引用本文件,不得各自发明证据语言。 > 本文件位于 `core/` 共享引用层(core 仅以 SKILL.md 作为安装依赖单元存在,本文件不会被独立触发)。 ## 1. 证据强度等级 ```text E0 用户初始陈述 ← 未经核实的用户描述(最低) E1 文档证据 ← PRD / 设计文档 / API 文档 / FAQ E2 代码证据 ← 文件:行、接口签名、数据结构 E3 运行/执行证据 ← 实际运行结果、测试执行结果、日志、截图 E4 交叉验证 ← 多来源互相印证(最高) ``` ## 2. 三类裁决规则 证据等级解决"信什么",裁决规则解决"冲突时听谁的": | 问题类型 | 裁决规则 | |---------|---------| | **静态问题**(代码是怎么写的) | E2 为王。E1 与 E2 冲突 → 默认以代码为准(测准声明),偏离记入附录并提交澄清 | | **动态问题**(系统实际怎么表现) | E3 为王。E2 与 E3 冲突(代码与运行不符)→ 这本身就是发现(死代码 / 路径未覆盖 / 环境差异),转澄清或缺陷记录 | | **意图问题**(应该是什么行为) | **用户在澄清环节的明确答复具有最终裁决力**,可推翻任何证据等级。裁决记入产物(澄清记录),后续 Skill 不得再用代码推翻已裁决项 | > 注意:E0(用户初始陈述)是低证据,但用户**在澄清环节的裁决**不是证据、是决策——两者必须区分。前者可被代码推翻,后者不可。 ## 3. 结论的状态标注 所有 Skill 的输出必须区分: ```text Fact 事实(有证据支撑的确定结论) Inference 推断(由证据合理推出,未直接验证) Risk 风险(可能出问题,尚无证据) Hypothesis 假设(待验证的猜想) Verified 已验证(有 E3 及以上证据) ``` ## 4. 标注格式 ```yaml finding: title: 删除用户后可能存在数据残留 evidence: level: E2 # E0–E4 source: user_service.go:124 # 来源:文件:行 / 文档名+章节 / 截图路径 / 运行日志 confidence: medium # high / medium / low status: hypothesis # fact / inference / risk / hypothesis / verified(待实测确认=hypothesis) ``` **禁止不加标注直接输出"这里存在 Bug"**——这是证据体系要消灭的幻觉式结论。 ## 5. 与各 Skill 产出的对齐约定 不同 Skill 的产出物按各自的自然形态携带上述标注,不要求改变文件结构,只要求字段齐全: | 产出物 | 标注方式 | |--------|---------| | `test-case-writing` 附录缺陷记录 Cx | 每条 Cx 含:现象 + `evidence.level`(E0–E4,代码审查发现通常为 E2)+ `evidence.source`(`文件:行`)+ confidence + status(待实测确认 / 已证伪等处置语义映射到 fact / hypothesis / verified) | | `test-case-writing` 附录高风险点 Dn | 每条 Dn 按 `risk-model.md` 评级,证据字段同上 | | `test-strategy` Risk Map | 每条风险(R1、R2…)强制带 evidence(level + source)与 confidence;**没有证据的风险评级视为无效评级** | | `bug-analysis` Root Cause | 根因结论标注 status(Inference → 读代码推断;Verified → 已复现验证 E3);影响范围逐条给来源 | | `requirement-analysis` 需求模型 rules | 每条业务规则标注 evidence(文档章节或 `文件:行`);用户裁决记入 open_questions 的裁决字段 | ## 6. 引用方式(双形态) 各 SKILL.md 的工作流步骤中标注"此时加载本文件"并给出相对路径,例如:`../core/evidence.md`。 该标注按部署形态有双重语义: - **真实宿主(按需加载形态)**:渐进披露——用到时才读,不预加载; - **注入式形态(评测/预注入部署)**:全部文件已在指令中,"此时加载"仅指示**执行时机** (该步骤开始执行本文件的规则),不触发任何读取动作,也不要因"文件已全文在场"而提前执行后续阶段的规则。 > 适配声明:skill 集的设计假设(qa 的上下文隔离、活跃指令 ≤ 1–2 个 skill)只对真实宿主成立; > 注入形态下全部规则同时在场,**靠阶段顺序而非加载机制防串扰**——执行时只服从当前所处阶段的指令。 -
executability.md 3.9 KB
# 用例可执行性标准(Executability) > "看似专业但不可执行"是当前最大质量问题。本标准是**框架级硬标准**,所有产出用例的 Skill(`test-case-writing`、`test-case-review`、`bug-analysis` 的回归用例建议)统一遵守。 > > 本标准同时是评测的一票否决指标:不可执行的用例,覆盖再全也计零分。 ## 1. 失败模式——AI 为什么会生成不可执行的用例 | # | 失败模式 | 典型表现 | |---|---------|---------| | 1 | 凭空想象系统 | 没见过真实页面 / 接口,凭 PRD 与通用经验虚构入口、字段、文案 | | 2 | 写给评审者而非执行者 | 追求覆盖矩阵漂亮,不关心执行者下一步能不能操作 | | 3 | 前置不可得 | 默认环境 / 账号 / 数据随手就有,不交代从哪获得 | | 4 | 判定不可操作 | 预期结果写"功能正常"这类模糊判定;异步行为无判定时限 | | 5 | 断言超出验证强度 | 两个样本就断言"全局唯一";UI 操作就断言数据库约束 | ## 2. 八条硬标准 1. **代码优先**——有代码必读代码,以实现为行为基线,杜绝凭文档想象(失败模式 1 的根治手段) 2. **执行模型判定**——有 UI 独立执行 / 无 UI 转"测试-开发协作清单",开工先判定 3. **具体数据**——操作步骤嵌真实编号 / 字段值,禁止占位符(`{xxx}`、`<xxx>`、`某某`) 4. **页面可达性**——每个入口写清从哪里到达;未知 → 澄清 + TODO 5. **判定时限**——异步行为必须写"多久不发生即判失败" 6. **断言范围 ≤ 验证强度**——用例名称与实际能证明的范围一致 7. **前置可得性**——环境 / 账号未知时列"TODO:向谁索取",不省略 8. **零上下文新人复述**——定稿自检:没读过需求、没人讲解的人只拿这份文件能开工 ## 3. 定稿自检清单(逐条执行) 以"零上下文新人"(没读过需求文档、没人讲解、只拿到这份文件)为标准: - [ ] 随机抽 3 条用例复述:验证什么、从打开什么系统开始怎么操作、怎么算通过——任何一步卡住(不认识某个词 / 不知道页面在哪 / 不知道怎么造前置状态 / 不知道等多久算失败)→ 修复正文或补导读区,而不是降低标准 - [ ] 导读四件套齐全:功能简介+角色表 / 环境与账号表(未知列 TODO 指明找谁)/ 术语表 / 图例 - [ ] 正文零代码内部:无 `文件:行`、SDK 符号、错误码常量、函数名(代码证据隔离在附录) - [ ] 操作步骤嵌具体数据,无占位符 - [ ] 每个被测入口首次出现时写明到达路径 - [ ] 含异步预期的用例逐条有判定时限 - [ ] 用例名称断言不超出步骤实际验证强度 - [ ] 每条用例可独立执行(有 UI)或可整包交开发协作执行(无 UI) ## 4. 违反即不可执行的红线(Eval 判分依据) 以下任一命中,该条用例在 Eval 中判为不可执行: 1. 操作步骤或预期结果包含占位符(`{xxx}`、`<xxx>`、"某数据") 2. 第一步操作指向不存在的入口(虚构页面 / 未说明到达路径且无 TODO) 3. 预期结果为"功能正常 / 正常显示"等无判定信息的表述 4. 异步预期无判定时限 5. 前置条件不可获得且无 TODO 指引(需要不存在的账号 / 数据却未说明来源) 6. 用例名称断言超出步骤能证明的范围 ## 5. 引用方式 各 SKILL.md 的用例产出 / 审查 / 定稿步骤标注"此时执行本文件全部检查项",相对路径 `../core/executability.md`。 第二个消费场景是**执行型 skill 的转换闸门**:`automated-e2e-testing`、`api-testing` 把存量用例转为自动化脚本前,对待转用例逐条过第 4 节红线——不可执行的用例直接翻译成脚本只会产出"幻觉自动化"(脚本能跑,测的不是真实系统行为);命中红线的用例先补齐再转,补不了的明确暂缓进遗留清单,不静默硬转。 -
pipeline-integration.md 10 KB
# 流水线集成与非交互运行约定(Pipeline Integration) > 全框架唯一的 CI/CD 与无人值守运行规范。qa-skills 各工作流默认为人机对话场景设计 > (⏸ 检查点等待用户裁决),本文件定义同一套工作流进入流水线(PR 冒烟 / 夜间回归 / > 发布卡点)与 headless 非交互模式时的降级约定:检查点怎么处置、产物落到哪里、 > 退出码如何区分基建故障与真实缺陷。 > > 三条设计约束: > 1. **人不在线,裁决不得虚构**——所有 ⏸ 检查点转为显式未决项,禁止 agent 代答 > 2. **基建失败 ≠ 测试失败**——两者分开编码为不同退出码,环境抖动不应半夜呼叫测试责任人 > 3. **产物必须可机读**——下游消费方(G 级预扫描 / 分流表 / 报告聚合)依赖稳定落盘形态 ## 1. 触发分层 | 层级 | 触发时机 | 范围 | 特征 | |------|---------|------|------| | PR 冒烟 | 每次 push / PR 更新 | P0 清单(变更基线关联用例) | 快失败,<10 分钟,失败量小 | | 夜间全量 | 定时调度(如每日 02:00) | P1 全量 + P2 抽样 | 批量失败常态化,分流主战场 | | 发布卡点 | release tag / 发版前置 | 全量 + 类型矩阵 full 档轴 | 门禁式:失败即阻断发布 | P0/P1/P2 清单本身的编制归属 `regression-testing` / `test-strategy`;本文件只约定 「哪一层触发跑哪个档」,不重复定义清单内容。 ## 2. 非交互三约定 ### 约定一:⏸ 检查点全部降级为未决项 clarify-pattern.md 定义的统一澄清提问卡片在人机对话场景等待用户答复;headless 场景 没有这轮对话,按以下优先序降级: 1. **有保守默认值的决策** → 采用最保守分支,并在未决项记录「默认采用 X,依据 Y」。 保守的定义:宁多报不漏报(扩大验证范围)、宁标 U 不硬编类别(分流口径)、 断言宁严勿松(放宽 catch 是把通过建立在侥幸上) 2. **无默认可言的意图问题**(需求歧义、A/B 定性分歧、Severity 争议)→ 记入未决项, 该项相关断言按**待验证**处理而非通过;产出物中逐条列出等用户回填 3. 未决项统一落盘 `{项目}/流水线状态.md` 的未决区(与各阶段跳过标记同一文件—— 续跑凭据与人工回流入口单点化),逐条沿用 clarify-pattern 卡片四要素 (问题来源 × 具体问题 × 影响 × 建议选项);人工回到线上后的第一个动作是在未决区 回填裁决,流水线续跑时按 qa 编排的断点规则增量消费这些裁决(见 qa/SKILL.md 断点检查) 4. **带留痕要求的裁决类产物**(如 test-strategy 的 `budget_review`):headless 下如实记 「headless 未决:采用保守裁剪/保留,依据 X」——不得写成用户已批准(铁律 1); 校验器只校验留痕存在与轴名出现,语义真伪由本条约束;人工回流后在原行补 `approved_by: 用户` 批注 > 铁律:非交互模式下 agent 给出的任何「用户裁决」都是伪造。evidence.md 第三类裁决 > 规则(用户答复具最终裁决力)在无人值守场景整体失效——裁断层保持空缺,由未决项承接, > 这是它与交互模式的唯一差异点。 ### 约定二:产物落盘规范 | 产物 | 路径 | 形态 | 主要消费方 | |------|------|------|-----------| | 结构化结果 | `{项目}/results/results.xml` | JUnit XML(reporter 配置见各执行 skill 工程约定) | G 级信号聚合、CI 展示层 | | 失败现场 | `{项目}/artifacts/{TC编号}/` | trace / 截图 / console log | S 级复核 | | 失败分流表 | `{项目}/失败分流_{日期}.md` | 分流表模板第 6 节 | 回流闭环(本文件第 4 节) | | 测试报告 | `{项目}/测试报告_{日期}.md` | report-template.md 全节 + 机读摘要节 | 人读 + 报告聚合系统 | | 未决项 | `{项目}/流水线状态.md` 未决区 | clarify-pattern 卡片格式 | 用户回填 → 续跑增量消费 | 两条命名纪律:① 日期后缀追加式留存,不清除历史(与分流表同一哲学:它是 D 类跨轮 追踪的最小数据形态);② 首个路径段固定为项目目录名,保证 CI 工件上传规则可以用 单一 glob 通配。 ### 约定三:退出码语义(基建与缺陷分离) 进程退出码是流水线分支判断的唯一机器接口,四值语义: | 退出码 | 含义 | 下游动作 | |--------|------|---------| | 0 | 全部通过(含 B1/B2 修订后的复验轮通过) | 绿灯归档 | | 1 | 存在 A 类真缺陷 | 告警开发 + 测试责任人,分流表随告警附上 | | 2 | C 类环境故障主导(≥50% 失败同因)或基建自身异常 | 自动原样重跑一次;再次为 2 则告警 DevOps,**不通知测试责任组** | | 3 | U 类未闭环超阈(U 类条数 / 本轮用例总数 > 10%)或产物自校验失败 | 告警测试责任人修流程/资产——属流程债,非产品缺陷 | 判定顺序 **2 > 1 > 3 > 0**:先排环境再谈缺陷,与分流判定树第 0 步同构。 退出码必须与分流表「结论分布」字段自洽——不允许出现退出码 1 但表中无 A 类、 或表内 A≥1 却退出码 0 这类矛盾(出现即按 3 处理)。 ## 3. host agent headless 调用范式 执行主体是安装了本套 skills 的 coding agent 以无头模式运行;两个 CI 平台的差异 只在编排语法,退出码语义与产物路径约定完全一致: ```yaml # GitHub Actions 夜间全量(GitLab CI 仅有 rules/artifacts 语法差异,语义相同) on: schedule: - cron: '0 18 * * *' # UTC 18:00 ≈ 东八区 02:00 jobs: nightly-regression: runs-on: ubuntu-latest # GitHub Actions 必填字段;GitLab CI 对应 image + script 段 steps: - run: | claude -p "$(cat .github/prompts/nightly-qa.md)" \ --allowedTools "Bash,Read,Write,Glob,Grep" \ --dangerously-skip-permissions # claude 非零退出码直接透传为本 step 退出码,供流水线按四值语义分支路由; # 切勿写成 "--dangerously-skip-permissions || EXIT=$?" 收尾——|| 后是赋值语句, # step 退出码恒为 0,四值语义整体失效。退码 2 的自动原样重跑: # 用独立 retry step 或外层包裹脚本实现,再按重跑结果取最终退码 - uses: actions/upload-artifact@v4 if: always() with: { name: qa-artifacts, path: '{项目}/' } ``` 三条 headless 纪律: 1. 非交互标志在命令行显式传入(`-p` / `--non-interactive` 等按宿主文档), 不允许依赖 TTY 探测猜测模式 2. 工件上传永远 `if: always()` / `when: always`——失败现场的产物比成功日志有价值得多 3. prompt 模板中显式写入「遵循 core/pipeline-integration.md 非交互约定」, 弱模型靠这句话找到三约定,而不是临场发挥 ### 3.1 大套件分片与门禁汇聚 全量套件超出单机时长预算(>30 分钟量级)时按文件分片并行,汇聚后统一进门禁: ```yaml # 分片:matrix 3 台 runner 各跑 1/3(GitLab CI 用 parallel:3,语义相同) jobs: e2e-shard: strategy: matrix: shard: [1, 2, 3] steps: - run: npx playwright test --shard=${{ matrix.shard }}/3 - uses: actions/upload-artifact@v4 if: always() with: { name: blob-report-${{ matrix.shard }}, path: playwright/test-results/ } e2e-gate: # required checks 只指向这个 job——分片 job 名带序号不稳定 needs: [e2e-shard] if: always() steps: - uses: actions/download-artifact with: { path: blob/ } - run: npx playwright merge-reports --reporter=html,junit blob/ # 汇聚后退出码仍按约定三(2>1>3>0)判定;分片内部红绿不直接进门禁 ``` 三条分片纪律: 1. 分片运行时 config reporter 增补 `['blob', { outputFileDir: 'blob/' }]`; 本地开发永远单机不分片——收敛反馈环 2. gate job 必须 `if: always()` 收齐全部分片再汇聚——任一分片被取消不能静默绿灯 3. 不为分而分:夜间套件 <15 分钟不分片,省 runner 也省汇聚开销 ## 4. 失败回流闭环 夜间/卡点的批量失败不是终点,回流分流完成「红墙 → 少量根因」的坍缩 (140 条失败常是 6 个根因穿马甲): ```text 夜间构建 fail → 失败分流(triage.md,失败 ≥3 条必走;退码 2 已先行吸收纯 C 批次) ├─ A 真缺陷 → bug-analysis 五字段 → 开发修复 → 修复关联回归(regression-testing) ├─ B1 预期变更 → 资产修订(附变更依据,条款一红线) ├─ B2 用例自身错误 → 就地修正脚本 ├─ C 环境残留 → 退码 2 重跑通道;仍红升级 DevOps └─ D flaky → 隔离标记 + 跨轮追踪,≥3 轮升级修根因 ``` 两条回流纪律: - 白天 PR 冒烟失败量小(通常 <3 条),按执行 skill 单条流程走,不付分流开销; 只有夜间/卡点批量失败才进分流 - 回流产生的资产修订(B1/B2)最迟下一工作日人工检视——防止绿灯建立在 过时资产上,也防止自动化修订本身引入新错误 ## 5. Common Mistakes | 错误 | 后果 | 正确做法 | |------|------|---------| | 非交互模式下代答检查点继续跑 | 伪造用户裁决污染证据链,结果不可信 | 三约定降级:保守默认 or 未决项,绝不代答 | | 所有失败统一退码 1 | 夜间环境抖动呼叫测试组,triage 声誉损耗 | 退码分离 2/1;C 主导批先进重跑通道 | | 只在 success 时上传工件 | 失败现场丢失,S 级复核无从做起 | 工件永远 always 上传 | | 结果仅打印在 stdout 日志 | G 级聚合无法进行,逐页翻日志的坑又回来了 | JUnit XML + 固定产物路径落盘 | | 夜间红了直接全员告警 | 告警疲劳,真缺陷淹没在一堆噪音短信里 | 先分流坍缩为根因,按退码定向路由接收人 | -
report-template.md 7.7 KB
# 测试报告模板(唯一来源) > 全框架唯一的测试报告模板。`qa` 收尾生成完整报告时引用本模板;`automated-e2e-testing` / `api-testing` 的执行报告、`bug-analysis` 的 Bug 条目均按本模板对齐格式,保证各阶段产物能直接拼装进最终报告。 > > 文件命名:`{项目}/测试报告_{日期}.md`(执行分报告可用 `测试报告_{来源}_{日期}.md` 区分)。 ## 模板正文 ```markdown # {项目名} 测试报告 — {日期} ## 1. 概览 - **测试范围**:{本次覆盖的功能 / 模块,对应需求模型与测试策略的 scope} - **测试环境**:{环境地址 / 版本 / 账号角色,未知项列 TODO 及索取对象} - **测试方式**:{手动 / E2E 自动化 / API 自动化 / 探索式,对应执行策略裁决} - **范围假设**:{系统级黑盒结论以单元/集成层(开发侧职责)已有保障为前提——已验证〔依据〕/ 未验证(列入 §6 未闭环事项)} - **总体结论**:{通过 / 有条件通过 / 不通过}——{一句话依据:P0 通过率、遗留 Critical 风险、未闭环澄清项} ## 2. 执行统计 | 优先级 | 用例数 | 通过 | 失败 | 阻塞 | 未执行 | |--------|--------|------|------|------|--------| | P0 | | | | | | | P1 | | | | | | | P2 | | | | | | > 失败用例逐条给出 Bug 编号;阻塞说明阻塞原因(环境 / 依赖 / 前置不可得)。 ## 3. Bug 清单 > **严重程度口径(与用例优先级消歧)**:Bug 严重程度用 **S0/S1/S2**(缺陷影响等级,见 `bug-analysis` 定级规则),与 §2 的**用例优先级** P0/P1/P2(冒烟/常规/边界)词汇彻底分离——"P0 用例失败"与"S0 Bug"不再有同词歧义。 > (同名区分:此处 S 系指 Bug 严重程度分级;类型决策矩阵的「S 级信号」〔semantic,语义信号复核〕是另一概念,见 `test-type-matrix.md`——两者同名不同义。) ### BUG-{序号}: {简要描述} - **严重程度**: S0(数据丢失/资损/安全、核心主路径不可用)/ S1(核心旁路不可用、部分降级)/ S2(体验问题) - **状态**: 新建 / 已修复待验证 / 已验证关闭 / 不予修复(附依据)——发现时标"新建",随回归结果同步(使用约定 6) - **复验轮次**: {整数,默认 0——仅状态为"已修复待验证"时填写,回归每失败一次 +1;其他状态省略此行} - **发现方式**: 自动化测试 / 探索性测试 / 业务熟悉探索 / 手动执行 / 代码审查(Cx 转) - **复现步骤**: 1. {以什么角色登录 / 准备什么数据} 2. {进入什么页面或调用什么接口} 3. {具体操作} - **预期行为**: {依据:需求文档章节 / 测试用例 TC 编号} - **实际行为**: {观察到的现象} - **证据**: 截图 `bug-{序号}-*.png` | API: `{METHOD} {URL} → {状态码}` | 控制台: `{错误}` | 日志: {文件与行} - **环境**: {URL} / {账号角色} / {浏览器或客户端版本} - **根因分析**: {bug-analysis 产出时填写:Root Cause + `文件:行` + evidence 等级;未分析则标 TODO} - **影响范围**: {bug-analysis 产出时填写:受影响功能/数据/用户/安全/修复波及五面(各面口径见 bug-analysis 影响分析);可选} - **Severity 依据**: {bug-analysis 产出时填写:后果 → S 级对照(S0/S1/S2,见 §3 口径注);可选} - **修复建议**: {bug-analysis 产出时填写:修复方向;可选} - **回归建议**: {bug-analysis 产出时填写:修复后应回归的用例编号或场景;未分析则标 TODO} ## 4. 风险与残留 | 风险编号 | 等级 | 状态 | 说明 | |---------|------|------|------| | R1 | High | 已验证 / 待验证 | {对应 Risk Map,注明验证用例 TC 编号} | ## 5. 回归摘要 - 本次回归范围:{必须回归 P0:…;建议回归 P1:…;可选 P2:…}(对应 `回归清单_{日期}.md`) - 回归结果:{通过率与遗留} ## 6. 未闭环事项 - {澄清未决项 / TODO(向谁索取什么)/ 遗留风险,逐条列} ## 7. 专项测试结果(类型域) > 类型域 handoff / 外部执行器 / blocked 轴的结果回收(决策见测试策略 type_scope,协议见 `test-type-matrix.md`)。无类型域专项时本节整体省略。 | 轴 | 决策/深度 | 执行方 | 结果摘要 | 证据等级 | 产物路径 | |---|---|---|---|---|---| | performance | include / full | k6 | P99 420ms(阈值 500ms,通过) | E3 | 压测报告_20260823.md | | i18n | exclude(无信号) | — | 未纳入,scanned 见策略 | — | — | > blocked 轴在此展示 TODO(向谁索取什么)——未回收前不得标"已覆盖";外部工具结果由 agent 归一化填入(发现 × 证据等级 × 阈值判定),消灭"策略写了移交、报告永远空白"的断链。 ## 附录:证据索引 - {截图 / 日志 / trace / API 抓包的文件路径与说明} ``` ## 使用约定 1. **qa 收尾**:按本模板生成完整报告,汇总各阶段落盘产物(需求模型 / 策略 / 用例 / 执行结果 / Bug / 回归清单的路径与结论) 2. **执行类 Skill**(automated-e2e-testing / api-testing):执行分报告至少包含 §2 执行统计 + §3 Bug 清单(Bug 条目字段与本模板一致,状态初始标"新建"),根因分析等五个扩展字段(§3 后五行)留 TODO 由 bug-analysis 补;api-testing 分报告可附结构覆盖摘要(零覆盖/低覆盖接口清单,见其运行结果纪律) 3. **bug-analysis**:Bug 条目的"根因分析 / 影响范围 / Severity 依据 / 修复建议 / 回归建议"为本 Skill 的填写范围(字段结构以本模板 §3 为唯一来源,bug-analysis 只补填写语义),结论按 `evidence.md` 标注 status 4. **报告是落盘产物**:追加不覆盖——新版本报告另存,历史报告保留(文件名含日期) 5. **专项结果回收**:qa 收尾汇总类型域专项状态(handoff / 外部执行 / blocked / exclude);执行结果按 §7 表归一化回收,exclude 轴以"未纳入 + scanned 见策略"呈现——类型范围决策在报告里可见、可审计 6. **Bug 状态同步**:状态随回归结果更新——回归通过 → 已验证关闭;回归失败 → 已修复待验证(附失败证据);裁决不修 → 不予修复(附依据)。qa 收尾按最新回归结果逐条同步(衔接 `regression-testing` §4),无回归记录的保持"新建"——消灭"Bug 报了但永远关不掉"的断链 7. **机读摘要**:qa 收尾与执行类 skill 生成分报告时,将下方"机读摘要片段"(占位符替换后)追加为报告末节的固定内容;流水线聚合与质量门禁只解析此节,人读内容不受影响。字段口径见 `pipeline-integration.md` 约定二/三 ## 机读摘要片段 ```yaml # 追加到报告末尾;与正文各节一一对应,冲突时以正文为准 meta: project: {项目名} date: {日期} source_run: {PR-smoke | nightly-full | release-gate} # 触发分层对照 pipeline-integration 第 1 节 summary: # ← §2 执行统计 total: {总数} passed: {通过数} failed: {失败数} blocked: {阻塞数} triage_distribution: # ← 失败分流表结论分布(未分流轮省略本节) A: 0 B1: 0 B2: 0 C: 0 D: 0 U: 0 exit_code_hint: 0 # ← 对照 pipeline-integration 约定三(0/1/2/3) open_items: {§6 未闭环条数} bugs: # ← §3 Bug 清单一行一条(id / severity / status) - {id: BUG-001, severity: S1, status: 新建} ``` ## 引用方式 各 SKILL.md 在报告产出步骤标注"此时加载本文件",相对路径 `../core/report-template.md`。 -
risk-model.md 4.2 KB
# 统一风险模型(Risk Model) > 所有 Skill 统一引用本文件。风险模型与证据体系(`evidence.md`)强制挂钩:**没有证据的风险评级视为无效评级**。 ## 1. 风险评分 ```text Risk Score = Impact × Likelihood ``` - Impact(影响)、Likelihood(可能性)各取 1–5,乘积 1–25 - **Change Scope(变更范围)与 Complexity(实现复杂度)不是乘子**,它们是估计 Likelihood / Impact 的启发式输入(见下表) ## 2. 两个因子的估计方法 | 因子 | 取值锚点 | 估计输入 | |------|---------|---------| | Impact(1–5) | 5 = 数据丢失 / 资损 / 安全;4 = 核心功能不可用;3 = 部分功能降级;2 = 体验问题;1 = 几乎无感知 | 失败后果分级:数据丢失 / 资损 / 安全 > 核心功能不可用 > 部分功能降级 > 体验问题 | | Likelihood(1–5) | 5 = 几乎必然触发;3 = 常规路径可触发;1 = 极端条件才触发 | 变更范围(diff 大小与涉及面)、实现复杂度、历史缺陷密度、代码证据(如边界未防护、事务不完整) | ## 3. 风险等级(与用例优先级是两套体系) ```text Critical 20–25 High 10–19 Medium 4–9 Low 1–3 ``` > **命名脱钩**:风险等级用 Critical / High / Medium / Low;用例优先级沿用 P0 / P1 / P2(`test-case-writing` 现行体系)。不用 P0–P3 给风险命名,避免同名歧义。 风险等级到用例优先级的**映射建议**(策略阶段可调整,但必须显式说明理由): | 风险等级 | 用例优先级要求 | |---------|---------------| | Critical | 必须有 P0 用例,且作为回归锚点 | | High | P0 或 P1 | | Medium | P1 | | Low | P2 或按需 | ## 4. Risk Map 标注格式 ```yaml risk: id: R1 feature: 用户删除 dimension: 数据一致性 # 功能域维度:权限/数据一致性/边界/状态流转/资损;与策略 scope 六轴的对应:权限→permission、边界→boundary、状态流转→state、数据一致性→data_consistency、资损→functional 主干(业务正确性缺陷的财务后果面);regression 轴的风险按回归触发的变更面(diff / Bug 修复)识别。类型域用矩阵轴名(并发/可靠/安全/性能/兼容/迁移/契约…),见 test-type-matrix.md impact: 5 # 删除后残留导致隐私与合规问题 likelihood: 3 # user_service.go:124 删除事务未覆盖关联表 level: High # 15 = 5 × 3 evidence: level: E2 source: user_service.go:124 confidence: medium status: hypothesis # 见 core/evidence.md 第 3 节状态标注(待实测确认=hypothesis) anchors: [TC-07-02] # 覆盖此风险的用例编号(派生索引,主从规则见下) ``` **双向引用主从规则**:`case.risk_ref`(Test Case Schema 字段)为权威方向,只在用例侧维护;`risk.anchors` 仅为 Strategy 文档里给人读的概览视图,**机器消费一律通过 Schema 的 `risk_ref` 反查**(`test-case-review` / `regression-testing` 等下游按此执行),不保证与最新用例同步——需要最新映射时从 Schema 重新生成,不手工编辑 anchors。 ## 5. 推导链 ```text Evidence(评级依据) ↓ Risk Map(每条风险:等级 + 证据 + 置信度) ↓ Test Strategy(风险决定测什么、测多深) ↓ Test Case(risk_ref 反向追溯到风险锚点) ``` ## 6. 与 `test-case-writing` 高风险点 Dn 的关系 `test-case-writing` 代码模式产出的附录高风险点 D1–Dn 是 Risk Map 在用例编写阶段的**局部实例**(聚焦代码审查发现的回归风险): - Dn 评级对齐本模型:Impact × Likelihood 评分 → Critical / High / Medium / Low 等级(替代旧的"最高/中高/中/低"表述;旧文件增量更新时顺带换算) - Dn 每条强制带 evidence(level + `文件:行` source)与通过判据 - 存在 `test-strategy` 产出的 Risk Map 时,Dn 优先映射到已有风险的 `risk_ref`,不另立编号体系 ## 7. 引用方式 各 SKILL.md 在需要风险评级 / 风险翻译的步骤标注"此时加载本文件",相对路径 `../core/risk-model.md`。 -
schema-extraction.md 6.8 KB
# Test Case Schema 抽取规则(唯一来源) > markmap → `测试用例.schema.yaml` 的抽取规则全框架唯一定义处。`test-case-writing` 编写/增量更新后抽取,`test-case-review` 修订后重新抽取,`regression-testing` / 执行层(`automated-e2e-testing` / `api-testing`)消费其结构化字段。规则变更只改本文件,不改各 SKILL.md。 ## 双轨原则 markmap 是给人的交付物与**唯一人工维护源**;Schema 是机器可读元数据层,由 skill 在加工时**从 markmap 单向抽取**,作为 Skill 间流转接口。**不要求人维护两份**,Schema 永远可由 markmap 再生。markmap 任何修改后必须重新抽取,两轨不一致以 markmap 为准。 ## 文件整体形态(三层) ```yaml meta: source_markmap: 测试用例_markmap.md # ← 抽取来源文件名 extracted_at: 2026-08-27 # ← 抽取日期(YYYY-MM-DD) strategy_ref: 测试策略.md # ← 上游策略文件名;无策略时省略此键 modules: # ← 一级模块表:module 字段的裁定依据 + 共享前置的唯一承载处 - module: "2. 营销活动" shared_preconditions: [] # ← 该一级模块正文标题下的 `> 前置:` 引用块逐行摘录(见 case-format.md §5) cases: [] ``` - **cases 平铺**为列表(不是按模块嵌套树)——消费方全靠字段过滤,嵌套树徒增解析成本;模块归属由用例的 `module` 字段表达 - **`module` 字段裁定**:取用例所挂**一级模块标题原文**(`## 2. 营销活动` → `"2. 营销活动"`,含编号与名称);子模块/细分类别不入该字段。TC 编号首段必须与一级模块编号一致(`TC-02-xx` 属于 module `"2. ..."`),校验器据此交叉核对 - **共享前置去重**:`modules[].shared_preconditions` 记各模块级共享前置一次;用例级 `preconditions` 只写该条额外需要的条件,与共享前置重复即违反去冗余规则(校验时若二者重复仅告警) ## 抽取规则(cases[] 逐字段) ```yaml id: TC-02-03 # ← 用例编号 title: # ← 名称行去编号与优先级 module: "2. 营销活动" # ← 所属一级模块标题原文(裁定规则见上节) priority: P0 | P1 | P2 # ← 名称行 [Px] 标注,缺省 P1 type: functional | boundary | exception | permission | regression | state | data | reliability | concurrency | security | compatibility # ← 按模块/子模块类别与用例内容判定;类型域四值(reliability/concurrency/security/compatibility)仅用于测试策略 type_scope 用例型轴产出的用例(映射见 test-type-matrix.md 第 12 节) execution_model: ui | dev-collab # ← 执行模型判定结果(协作五段式 → dev-collab) smoke: SMOKE-1 # ← 名称后(SMOKE-n),非冒烟省略 preconditions: [] # ← 该条特有的前置条件(共享前置在 modules 表,不在本字段重复) steps: [] # ← 操作步骤;dev-collab 保留「请开发执行」标注 expected: [] # ← 预期结果(含判定时限原文) test_data: {} # ← 步骤中嵌入的具体测试数据,按步骤号组织 {step: 数据描述}(如 {2: "券码 NEW50-100"});expected 中的期望值不算 test_data。只用具体值(占位符是可执行性红线),多步骤共用一组入参时也拆到触发它的步骤号下 risk_ref: # ← 关联风险编号:Risk Map 的 R1…(无策略时 Dn 的 D1…) code_refs: [] # ← 附录「代码证据清单」中该 TC 的 文件:行 列表。**模式相关必填**:代码模式(markmap 有测准声明)应有非空列表——没有代码位置就没有被审查资格,确无一比一对应的实现可注明实现形态;纯文档模式一律空列表(编造指涉属幻觉证据) evidence: # ← 该用例依据的证据(level E0–E4 / source / confidence,见 core/evidence.md)。代码模式必填三件套;纯文档模式下 evidence 允许省略或值为 null(此时 level 上限 E1,准确性受限提示已由流程给出) tags: [] # ← 两类合法值:可测试性标注 [需真机]/[需Mock]/[需专业环境],以及类型域轴标签 [并发]/[可靠]/[安全]/[兼容]/[迁移]/[集成]/[国际化](两组含义均见 case-format.md §6);其余自由文本标签会在校验时收到告警 automation: supported: yes | no | partial # ← 按 execution_model + tags 推断(ui 无特殊标签→playwright;dev-collab→api 或 manual;[需Mock]/[需真机]/[需专业环境] 至多 partial) framework: # playwright / api / manual status: active | changed | deprecated # ← 增量更新标记([已变更]/[已废弃]) ``` ## YAML 转义纪律(防空解析) title / steps / expected 等字段值含引号或冒号时必须转义——双引号值内的 `"` 写成 `\"`,或整体改用单引号包裹(内部单引号双写 `''`);禁止在双引号值内裸放引号(如 `"满100减20"券"` 是非法 YAML,一条解析失败会中断下游全部消费)。长文本建议用 `>-` 块标量。 ## 抽取后校验(强烈建议) 抽取/修订重抽后运行 `../core/scripts/validate_schema.py`(无第三方依赖,路径相对消费本文件的 SKILL.md 所在目录): ```bash # 基础校验:YAML 可解析(转义纪律)+ TC 编号一致性 + 占位符检查 python3 ../core/scripts/validate_schema.py 测试用例_markmap.md 测试用例.schema.yaml # 推荐:上游存在测试策略时叠加风险覆盖门禁——策略 Risk Map 中全部 Critical/High 风险 # 必须被至少一条用例的 risk_ref 反向覆盖(零覆盖 = 报错,证据链最后一环); # 有代码仓库时可叠加 --repo-root 抽查 code_refs 中 "文件.ext:行号" 指涉的真实性(缺失告警) python3 ../core/scripts/validate_schema.py 测试用例_markmap.md 测试用例.schema.yaml \ --strategy {项目}/测试策略.md --repo-root {被测仓库根} ``` 校验说明:零依赖环境下 YAML 解析降级为基础 lint(引号配对 / 裸引号)。`--strategy` 同时核对 risk_ref 是否指涉 Risk Map 中存在的编号(未知名 → 告警提示疑似笔误)。 ## 存量迁移 已有 markmap 用例文件按同规则抽取(TC 编号、优先级、模块结构都是现成锚点),不需要人工重写。历史上以 `test_case:` 为根的单对象 / 嵌套形态仍可被校验器解析(按 id 递归收集用例),但重新抽取时一律换成上文三层形态。 ## 引用方式 各 SKILL.md 在抽取 / 重新抽取步骤标注"此时加载本文件",相对路径 `../core/schema-extraction.md`。 -
SKILL.md 2.2 KB
--- name: core slug: core displayName: QA 共享知识库 version: 0.8.1 description: "qa-skills shared knowledge base — dependency, NOT triggerable: executability standards, evidence grading, risk model, type matrix, templates, scripts. Never invoke standalone; always install with the skills, or references break. 共享知识库(依赖单元,非触发 skill):承载全系列引用的方法/模板/脚本。任何测试任务不要独立触发;装其他 skill 必须连装。" --- # qa-skills 共享知识库(core) 本目录是框架的公共层:不定义工作流、不面向用户触发,仅被 `skills/` 下其余 12 个 skill(qa / requirement-analysis / test-strategy / …)以相对路径 `../core/<file>` 按需引用。单一维护源,多 skill 复用。 ## 共享文档 | 文件 | 内容 | |------|------| | `evidence.md` | 证据分级标准(E0–E4)与状态标注 | | `risk-model.md` | 风险评级模型(评级强制挂证据) | | `executability.md` | 用例可执行性硬标准(8 条,一票否决项) | | `testing-principles.md` | 测试原则 | | `case-format.md` | 用例编号与格式规范 | | `coverage.md` | 覆盖评估口径 | | `schema-extraction.md` | schema.yaml 抽取规则 | | `clarify-pattern.md` | 澄清检查点模式 | | `test-type-matrix.md` | 类型决策矩阵(十轴全轴必答) | | `triage.md` | 失败分流规范(四分类判定树 + G/S 信号 + 反吞没条款,失败 ≥3 条触发) | | `pipeline-integration.md` | 流水线集成与非交互运行约定(⏸ 检查点降级、产物落盘、退出码语义、CI 触发分层与失败回流) | | `report-template.md` | 测试报告模板 | | `methods/*.md` | 设计方法细则(边界 / 数据驱动 / 权限 / 状态机)与执行层造数模式(数据工厂) | | `scripts/*.py` | Schema 校验器、类型信号扫描器 | ## When NOT to Use - 任何具体测试任务(写用例、定策略、查 Bug 等)**都不要触发本 skill**: 它不包含工作流,独立使用没有产出。对应任务请用其余 12 个 skill 之一。 - 本 skill 仅在两种情况下被触及:① 其余 skill 工作流按需引用上述文件; ② 安装/校验场景(作为安装依赖单元被安装器识别)。 -
test-type-matrix.md 17.5 KB
# 测试类型决策矩阵(test-type-matrix) > 类型域测试决策的**唯一真相源**:test-strategy 全轴扫描时**按组加载**本文件对应小节,产出 type_scope;test-case-writing 消费 type_scope 时按第 12 节映射产出。决策原则(全轴必答 / include 挂信号 / exclude 挂理由 / depth 与 execution 分离 / 单一真相 / 弱模型优先)与工作流落地见 test-strategy 的 SKILL.md;type_scope 的 Schema 结构与 V1–V5 校验规则以 `../core/scripts/validate_schema.py` 的 docstring 与实现为权威定义。 **矩阵版本**:v1(2026-08-23)。轴集合增删必须升版本号。 ## 0. 组索引(分轴组推进,按组加载) | 组 | 轴(本组只加载这些小节) | 组内交付核对 | |---|---|---| | A 资金与正确性 | 轴 1 性能效率 · 轴 2 业务安全 · 轴 4 并发一致性 | 三轴全部有决策 | | B 依赖与变更 | 轴 3 可靠性 · 轴 9 迁移与升级 · 轴 10 契约与集成 | 三轴全部有决策 | | C 前端体验 | 轴 5 兼容性 · 轴 6 无障碍 · 轴 7 视觉一致性 · 轴 8 国际化 | 四轴全部有决策 | ## 1. 使用规程(每组开扫前执行) 1. **G 级扫描**(有代码仓库时,一次运行三组共用):`python3 ../core/scripts/scan_signals.py <仓库路径>`(路径相对 test-strategy 的 SKILL.md 所在目录)→ 每轴 G 级信号(文件:行)+ 预填表。**预填仅基于 G 级;exclude 永不预填**(防橡皮图章) 2. **逐轴决策**(受限选择,不自由生成):需求信号(需求模型 / PRD)→ G 级信号核对 → **S 级信号照单复核**(本轴代码信号清单中标〔S〕的项,逐项读代码确认)→ decision(include / exclude / handoff)→ depth(full / standard / light) 3. **exclude 门槛**:需求 + G 级 + S 级三路全灭才可 exclude;scanned 必须落盘 **G 级脚本输出 + S 级复核结论双清单**(V3 校验)——S 级未复核不得 exclude,否则脚本盲区被制度化为漏测 4. **组内交付核对**:本组全轴有决策再进下一组;十轴完成 → 第 13 节深度校准 + 预算排序 5. **无代码仓库**:跳过 G / S 级,仅需求信号决策;exclude 理由注明"无代码仓库,需求信号未命中"(决策可信度降级,报告标注) **轴空间封闭**:类型域就是这十轴,不自行新增。可用性 / UX 主观体验、渗透测试、SAST / 依赖扫描、产品可维护性、功能安全(safety)**不设轴**——可用性与 UX 的判定主观性强,走探索测试与人工评审;渗透测试依赖专业人工与授权环境,右移到安全专项;SAST / 依赖扫描是开发侧 CI 工具,在本矩阵中只作轴 2 / 轴 10 的**信号来源**而非独立轴;产品可维护性与功能安全超出测试范畴。新增轴 = 矩阵升版本。 ## 2. 域间裁决(重叠必裁决) | 重叠 | 裁决 | |---|---| | 功能域 permission × 轴 2 业务安全 | permission 管授权功能正确性(有权限的人行为对不对,越权用例归它产);业务安全管未授权视角防御(不该拿的拿不拿得到、敏感数据漏不漏、注入面、认证会话)。permission 轴 depth ≥ standard 是轴 2 的**内部纳入信号**;同一批越权用例不得两轴双计数 | | 轴 4 并发一致性 × 轴 1 性能效率 | 正确性 bug(超卖 / 重复 / 丢更新)归轴 4;容量 / 延迟归轴 1。同一次并发执行可同时给两轴供 E3 证据(执行复用,决策分离) | | 功能域 exception/boundary × 轴 10 契约与集成 | 功能域为主:异常 / 边界场景下的业务行为正确性(系统自身处理逻辑对不对)归功能域用例产出;轴 10 为辅:只产契约差异项(对外接口的错误码 / 响应结构 / 超时语义与契约不符的部分)。同一接口的同一路径不得两处双计数 | ## 3. 轴 1 性能效率(performance) - **需求信号**:SLA / P95 响应时间承诺;大促、秒杀、抢购场景;预计并发用户数;性能验收指标 - **代码信号**:无分页全量查询;循环内远程调用(N+1)〔S〕;缓存依赖(Redis 命中率假设);锁竞争热点〔S〕;消息积压消费逻辑〔S〕(积压 / 消费速率为语义判断项,扫描器不扫,照单读代码复核) - **默认档**:软默认——无信号 exclude(记录扫描结果);有 SLA 或容量信号 → include - **档位语义**:full = 压测模型设计(用户旅程 × 到达率阶梯)+ 执行 + 瓶颈归因报告;standard = 核心接口基准(单接口阶梯加压)+ 阈值判定;light = 代码级性能审查(分页 / 缓存 / N+1 / 锁,逐项出 E2 证据清单) - **执行归属**:三分——① agent 直接承接:executor 记 agent,standard 起步按 api-testing 的 k6-conventions 工程约定生成并运行压测脚本(light 代码级审查可闭环不变);② 移交外部执行:executor 记 k6 / locust + handoff_ref(V5 强制),承接方可附脚本草稿;③ 执行前提不可得:R5 记 blocked + todo - **成本因子**:独立压测环境、可重置数据、基准噪音控制 - **消费方式**:脚本型——standard 及以上不产手动用例,产压测模型与执行物进执行策略裁决;light 产代码审查清单条目(Cx 通道) ## 4. 轴 2 业务安全(security-business) - **需求信号**:多角色 / 权限层级;隐私数据(手机号 / 身份证 / 地址);资金操作;合规要求(个保法 / GDPR);**内部信号**:功能域 permission 轴 depth ≥ standard - **代码信号**:鉴权中间件覆盖面缺口〔S〕;对象级授权缺失模式〔S〕(user_id 来自请求参数而非会话);可枚举 ID 直接引用;敏感字段明文返回或落日志;SQL 拼接 - **默认档**:**硬默认**——Web / API 系统一律 standard,无 R4 exclude 出口(成本极低 × 价值极高);其余九轴均为软默认 - **档位语义**:full = 未授权视角全矩阵(匿名 + 低权角色对全部高危资源的可达性探测)+ 敏感数据流追踪 + 注入面;standard = 高危资源越权抽样 + 注入面 / 敏感暴露扫描;light = 设计层审查(Cx 记录)。**越权功能正确性用例归功能域 permission 轴产出**(第 2 节裁决),本轴不重复产 - **执行归属**:agent(复用 API 测试基建与测试账号矩阵;方法基线 `methods/permission.md`) - **成本因子**:低——测试账号矩阵是唯一前置 - **消费方式**:用例型——standard 及以上产手动用例(type: security,标签 `[安全]`);light 产 Cx 审查条目 ## 5. 轴 3 可靠性(reliability) - **需求信号**:可用性承诺(9x%);降级预案;容灾要求;"不能丢消息"类硬约束 - **代码信号**:重试逻辑与重试上限;超时配置;消息队列消费与 ack;断路器;事务边界;补偿 / 回滚逻辑 - **默认档**:软默认——有异步 / 第三方依赖信号 → standard;无 → light - **档位语义**:full = 故障注入矩阵(依赖宕机 / 超时 / 拒绝 / 脏数据)+ 恢复验证;standard = 超时 / 重试 / 幂等 / 降级专项用例("失败 → 重试 → 恢复"原则的类型化落地);light = 清单审查(超时有无 / 重试上限 / 幂等键 / 消息 ack 语义) - **执行归属**:standard 及以下 agent;故障注入 = agent 设计 + 可控环境执行 - **成本因子**:故障注入需可控环境;standard 无特殊成本 - **消费方式**:用例型——standard 及以上产 type: reliability 用例(标签 [可靠]);light 产清单条目 ## 6. 轴 4 并发一致性(concurrency) - **需求信号**:库存 / 名额 / 配额;抢购;"全局唯一"类约束;多人同时编辑 - **代码信号**:check-then-write 模式(先查后写无锁)〔S〕;共享可变状态〔S〕;扣减逻辑;依赖唯一约束兜底的写入路径 - **默认档**:软默认——有共享可变资源信号 → standard;无 → light - **档位语义**:full = 竞争窗口分析 + 并发用例矩阵(同一资源 × 全部竞争写入路径)+ 真实并发执行;standard = 关键竞争点并发用例(衔接 api-testing 并发能力);light = 竞态模式代码审查 - **执行归属**:agent - **成本因子**:可重置数据 - **消费方式**:用例型——standard 及以上产 type: concurrency 用例(标签 [并发]);light 产清单条目 ## 7. 轴 5 兼容性(compatibility) - **需求信号**:明确支持的浏览器 / 设备清单;企业客户旧环境(旧内核);跨端一致性要求 - **代码信号**:有前端(前端源码文件计数 ≥3 即命中——扩展名清单与阈值以 `scripts/scan_signals.py` 实现为准,含 .vue/.jsx/.tsx/样式族等;package.json 前端依赖、移动端信号同计为有前端——pubspec.yaml(Flutter)、react-native/@tarojs/@dcloudio 跨端依赖、project.config.json 小程序配置);浏览器特性 API 使用(IntersectionObserver 等);UA 判断分支;响应式断点;多端平台分支(Platform.OS / kIsWeb / wx.getSystemInfo / `#ifdef MP-WEIXIN` 等);系统版本门槛(minSdkVersion / deployment_target 等) - **默认档**:软默认——有前端 → light;有明确支持清单 → standard - **档位语义**:full = 支持矩阵逐格(浏览器 × 设备 × 分辨率 × P0 路径);standard = 支持清单矩阵 × P0 路径;light = 最新 Chrome / Safari / Edge / Firefox 冒烟 - **执行归属**:agent + Playwright 项目矩阵(现成接入片段:automated-e2e-testing 工程约定第 14 节) - **成本因子**:多浏览器 CI runner;真机(移动端超出 v1 范围) - **消费方式**:用例型——standard 及以上产 type: compatibility 用例(标签 [兼容]);light 产冒烟清单条目 ## 8. 轴 6 无障碍(accessibility) - **需求信号**:政企 / 教育政务客户;无障碍合规要求;投标条款 - **代码信号**:有前端即最低信号(语义化标签缺失、img 无 alt、键盘不可达、表单无 label——由 axe 扫描器核实) - **默认档**:软默认——有前端 → light - **档位语义**:full = WCAG 2.2 AA 逐页审计 + 键盘全路径 + 读屏抽样(读屏归人工);standard = axe 全页扫描 + 违规分级 + 关键流键盘走查;light = axe 扫描 P0 页面 - **执行归属**:agent + axe-core(Playwright 集成;现成接入片段:automated-e2e-testing 工程约定第 12 节) - **成本因子**:极低 - **消费方式**:审查 / 脚本型——任意档位产 axe 扫描任务与违规清单(客观违规项分级呈现);full 的读屏抽样子项标注人工 - **边界**:主观判定类(对比度阈值争议、读屏体验)标人工复核,agent 只出客观违规清单 ## 9. 轴 7 视觉一致性(visual) - **需求信号**:品牌规范验收;设计稿交付(Figma);视觉回归要求 - **代码信号**:有前端即信号;已有截图测试(toHaveScreenshot 等) - **默认档**:软默认——有前端 → light - **档位语义**:full = 全页面截图基线 + 逐变更 diff;standard = 关键页基线 + diff;light = P0 页面截图存档供人比对(不自动判) - **执行归属**:agent + Playwright screenshot(现成接入片段:automated-e2e-testing 工程约定第 13 节) - **成本因子**:低;基线维护与动态内容遮罩规则(时间 / 头像 / 随机推荐位)是主要复杂度 - **消费方式**:脚本型——standard 及以上产截图基线执行物;light 产截图存档任务 - **防 flaky 硬约束**:自动 diff 必须先声明遮罩规则,未声明遮罩的 diff 失败不判 Bug ## 10. 轴 8 国际化(i18n) - **需求信号**:海外 / 多语言市场;RTL 语言;跨时区用户群;本地化交付 - **代码信号**:i18n bundle / locale 目录(扫描器探测);日期货币格式化库;硬编码文案(S 级复核);时区处理逻辑 - **默认档**:软默认——无信号 exclude(记录扫描结果);有信号 → standard - **档位语义**:full = 伪本地化全量 + RTL 布局走查 + 格式矩阵(日期 / 货币 / 电话 / 时区 / 复数规则);standard = 目标语言集核心流 + 格式抽样;light = 硬编码文案扫描 + 代表性页面伪本地化 - **执行归属**:agent - **成本因子**:翻译数据依赖(伪本地化不依赖) - **消费方式**:用例型——standard 及以上产 type: functional 用例加标签 [国际化];light 产扫描条目 ## 11. 轴 9 迁移与升级(migration) - **需求信号**:存量系统迭代;数据结构变更公告;灰度共存 / 版本升级 - **代码信号**:DB migration 文件(扫描器探测 migrations / alembic / flyway 目录);数据回填脚本;双写逻辑〔S〕;API 多版本共存 - **默认档**:软默认——有 migration 文件 → standard;无 → 存量系统 light / 绿地项目 exclude(记录判断依据) - **档位语义**:full = 升级路径矩阵(版本 × 数据形态)+ 回滚验证 + 新旧数据共存探测;standard = 代表性升级用例 + 回滚一例;light = migration 脚本审查(可回滚性 / 兼容性 / 回填幂等) - **灰度共存专项(需求信号命中灰度时并入对应档位一并交付)**:新版本自身功能之外覆盖三条链路——① 灰度路由正确性:命中小流量规则的请求进新版、未命中留在旧版(用例各自验证一侧);② **出界处置**:与用户确认哪些指标越界即触发暂停 / 回滚(错误率 / 资损告警阈值等),并对"越界 → 暂停 → 回滚 → 数据一致"至少演练一次;③ **出界留痕**:每轮灰度的范围、比例、指标快照与出界事件记录落盘,作为下一步扩大灰度的依据——禁止凭印象全量 - **执行归属**:agent - **成本因子**:可重置的存量数据快照 - **消费方式**:用例型——standard 及以上产 type: functional 用例加标签 [迁移];light 产脚本审查条目 ## 12. 轴 10 契约与集成(contract-integration)+ 消费方式映射 ### 轴 10 契约与集成 - **需求信号**:第三方依赖清单;对外 API 的消费方;微服务边界;webhook - **代码信号**:外部 HTTP / gRPC 调用;消息契约;OpenAPI 定义变更;回调处理 - **默认档**:软默认——有外部依赖 → standard;无 → exclude(记录扫描结果) - **档位语义**:full = 消费者驱动契约(Pact 式)+ 依赖故障矩阵(超时 / 错误码 / 格式变化 / 限流);standard = 关键依赖 mock 与真实双跑 + 异常分支用例;light = 调用面清单 + 错误处理审查 - **执行归属**:agent(schema 一致性 / 负向 fuzzing 按 api-testing「契约与 schema 一致性」节);契约测试框架(Pact 式多消费者场景)执行为移交候选 - **成本因子**:mock / 沙箱可用性 - **消费方式**:用例型——standard 及以上产 type: functional 用例加标签 [集成];契约执行物进执行策略裁决 ### 消费方式映射总表(test-case-writing 消费 type_scope 时执行) | 消费方式 | 适用(standard 及以上;例外档位见格内标注) | 产出 | |---|---|---| | 用例型 | 轴 2(type: security,标签 `[安全]`)、轴 3(reliability)、轴 4(concurrency)、轴 5(compatibility);轴 8 / 9 / 10(type: functional + 标签) | 手动用例进 markmap,type / 标签见左 | | 脚本型 | 轴 1(压测模型 + 执行物)、轴 6(axe 扫描任务与违规清单,**任意档位**)、轴 7(截图基线) | 不产手动用例,产执行物进执行策略裁决 | | 审查型 | 各轴 light 档 | 扫描 / 审查清单条目(Cx 通道),不进用例正文 | > exclude 轴不产出;handoff 轴只产移交包。depth=light 的轴 2 / 3 / 4 / 5 也按审查型走 Cx 通道。 ## 13. 深度校准与停止准则 ### 升降档规则(R1–R6;唯一声明的冲突裁决:R6 > R1) > 编号区分:本表 R1–R6 是**升降档规则**编号;Risk Map 中的 R1、R2… 是**风险条目**编号——两者同名不同义,以上下文为准。 | 规则 | 触发条件 | 动作 | |---|---|---| | R1 风险升档 | 该轴覆盖的场景挂 Critical 风险 → full;High → 至少 standard | 升档,rationale 挂风险编号 | | R2 双源信号 | 同轴命中 ≥2 类独立信号(需求级 + 代码级) | 升一档 | | R3 历史缺陷 | 该轴维度历史 Bug ≥3 或出过线上事故 | 升一档 | | R4 无信号降档 | 需求 + G 级 + S 级扫描全灭(硬默认轴无此出口) | 可降至 exclude;G + S 双清单落盘 | | R5 成本门 | 执行前提(环境 / 数据 / 工具)不可得 | **执行降级不决策降级**:depth 保持,execution_status 记 blocked + todo 向谁索取 | | R6 预算约束 | full 轴数 > 3(两域合并计) | 按风险排序裁剪至 ≤3,记录排序依据。被裁剪的 Critical 轴触发**预算裁决检查点**(用户可扩预算,扩预算时 depth_budget 记 budget_review) | ### 停止准则 每轴的**档位语义就是停止准则**(§3–§12 各轴写死的动作清单勾完即停);策略落盘时生成足够性声明:逐轴列档位动作清单与完成状态。禁止"再测测看"式开放式深挖——想加深必须升档并说明触发了哪条 R 规则。 ## 14. 引用方式 test-strategy 工作流第 4 步按组加载本文件对应小节(`../core/test-type-matrix.md`);test-case-writing 阶段一消费 type_scope 时加载第 12 节映射表。本文件不复制进任何 SKILL.md(单一真相红线)。 -
testing-principles.md 3.6 KB
# 测试原则与设计方法选择 > 跨 Skill 共享的测试方法论。`test-strategy` 用它决定测什么类型 / 多深;`test-case-writing` 用它选设计方法与组织结构。方法级落地细节在 `methods/`,覆盖度检查表在 `coverage.md`。 ## 1. 测试原则 1. **一条用例只测一个点**,预期结果明确唯一;"或 A 或 B"拆成两条 2. **风险驱动**:围绕风险设计测试,而不是机械生成——风险 → 策略 → 用例的推导链(见 `risk-model.md`) 3. **证据先行**:所有结论标注证据等级与来源(见 `evidence.md`) 4. **代码优先**:有代码必读代码,以实现为行为基线(测准声明) 5. **先澄清再动手**:模糊 / 矛盾之处向用户提问,不硬猜 6. **给人看、给人执行**:用例是给测试工程师执行的操作指令,不是覆盖度展示品(见 `executability.md`) 7. **失败之后要有重试**:每种失败 / 拦截场景,配"失败 → 再试 → 恢复"用例 8. **逆向操作成对验证**:创建↔删除、分配↔释放、导入↔撤销,每对都要有用例 ## 2. 设计方法选择(按功能特征选方法,不强制状态机) | 功能特征 | 设计方法 | 核心动作 | |---------|---------|---------| | 明显状态流转 | State Machine | 提取状态 → 事件 → 新状态;按状态节点分模块;每条边一个用例;非法转换配拦截用例 | | 输入输出型功能 | Equivalence Partitioning + Boundary Value Analysis | 划分有效/无效等价类;边界取值:空 / 最小 / 最小-1 / 最小+1 / 最大 / 最大+1 / 超大 | | 权限系统 | Role × Action × Resource | 行为权限矩阵逐格核对;资源级隔离(A 的数据 B 不可见);角色变更后权限即时性 | | API | Parameter Matrix | 参数 × 类型/必填/边界/默认值逐格;多参数组合显式降档(全组合 → 成对 → 风险挑选,见 `methods/data-driven.md` 第 2 节);鉴权 / 幂等 / 并发 / 错误码 | | 复杂业务流程 | Workflow | 端到端主链 + 每环节失败分支 + 中断恢复 + 多轮累积 | | 数据转换 | Input → Transform → Output | 典型输入 / 畸形输入 / 空输入 → 逐类核对输出;转换保真(读回一致) | | 历史 Bug 较多 | Regression-focused | 每个历史 Bug 一条回归用例;聚类找系统性薄弱区加防 | 各方法的落地细则(按需加载):边界 / 等价类 → `methods/boundary.md`;参数矩阵 / 导入 / 数据转换 → `methods/data-driven.md`;权限矩阵 → `methods/permission.md`;状态机组织 → `methods/state-machine.md`。 ## 3. 二阶交叉覆盖(一阶枚举的补漏公式) 一阶枚举(每条规则 / 每条状态边一个用例)做得再全,漏的通常是三类交叉: 1. **写入路径 × 校验规则**:同一条校验在创建 / 编辑 / 重新提交 / 导入等**每条写入路径**上是否都生效(校验常只挂在创建,编辑路径绕过校验的 bug 高发) 2. **失败 × 重试**:每种失败 / 拦截发生后,重试能否恢复正常 3. **标识 × 重复**:名称 / 编号等用户可见标识字段重复提交时的唯一性行为 ## 4. 覆盖维度索引 覆盖度检查的完整维度清单(19 维 + 横切可执行性)由 `coverage.md` 维护,`test-case-review` 独立审查时同样引用该清单(`test-strategy` 的范围决策走 `risk-model.md` 的风险维度,不直接消费该清单)。 ## 5. 引用方式 各 SKILL.md 在选择测试类型 / 设计方法的步骤标注"此时加载本文件",相对路径 `../core/testing-principles.md`。 -
triage.md 13.6 KB
# 统一失败分流规范(Failure Triage) > 全框架唯一的**批量失败首轮分流**规范。解决两个对称的时间黑洞:把 N 条自动化失败逐条当 Bug > 全流程处理(误报淹没真问题),或重跑变绿全部放过(真回归漏网)。一轮执行的失败集合先按四类 > 归类、逐条给依据、路由去向,再进入各自下游。 > > 失败处理的三层能力栈,本文件只占中间层: > > | 层 | 职责 | 归属 | > |----|------|------| > | 运行时技巧层 | 执行中单条失败的即时定性(first-run-flaky 规则、等待策略) | 各执行 skill 工程约定(如 playwright-conventions 第 11 节) | > | **分流层(本文件)** | 一批失败的首轮分类 + 路由去向 | 消费方在失败 ≥3 条时加载本文件 | > | 深挖层 | 已确认 Bug 的根因/影响/Severity/修复建议 | `bug-analysis` | ## 1. 触发条件与输入输出 - **何时使用**:一轮执行(自动化全量 / CI 夜间任务 / 回归清单执行 / 回归复验)结束后,失败条数 **≥3 条**。1–2 条失败不值得分流开销,直接按执行 skill 单条流程处置。 - **输入**:本轮结果清单(TC 编号 × 通过/失败)+ 失败现场产物(trace / 截图 / 控制台日志 / API 请求响应记录 / JUnit results.xml 等结构化输出) - **输出**:`{项目}/失败分流_{日期}.md`(模板见第 6 节)——逐条给分类 + 依据 + 去向;其结论分别流向上游各 skill 与测试报告对应章节 - **追加式留存**:分流文件按日期累积不清除——它是 D 类跨轮追踪的最小数据形态(见第 5 节),不需要额外数据库 ## 2. 四分类定义(+U) | 类别 | 判定语义 | 典型形态 | 定类最低证据 | 去向 | |------|---------|---------|-------------|------| | **A 真缺陷** | 产品侧行为与预期不符且变更非预期;实现未变而结果变了同样归此 | 断言失败但预期仍有效;数据不一致;状态卡死 | E3 运行证据 + S 级复核通过(第 4 节) | 执行 skill 报告 §3 记 Bug 条目 → 根因五字段移交 `bug-analysis` | | **B 资产问题** | 失败由**测试资产侧**原因造成,产品行为无缺陷:B1 产品变更属预期但用例预期未跟上;B2 用例自身写错(脚本选择器永久失效、断言抄错字段、步骤拼错) | expected diff 恰为新文案/新默认值;页面重构后选择器过期 | B1 必须附**变更依据**(commit / 需求文档章节或用户裁决);B2 给出写错位置 | 更新用例或脚本:B1 → `test-case-writing` 增量修订并留变更依据;B2 → 就地修正脚本 | | **C 环境与依赖故障** | 失败由被测系统之外的运行环境造成:服务不可达、证书/账号失效、部署进行中、共享数据被污染 | HTTP 5xx 密集、连接拒绝、登录态全挂、同批大面积同形态失败 | E3 错误形态聚集 + 单一环境事件定位 | 记环境事件(一条),修复后**受影响批次原样重跑**(见第 5 节反吞没条款二) | | **D 不稳定(flaky)** | 同一输入结果不定:竞态等待不足、清理不彻底、时序敏感 | 首跑失败原样重跑通过;历史翻灯交替 | E3 重跑对照(重跑只用于定性,不算通过) | 按 owning skill 工程约定处置(短期 fixme + 排期修根因),分流表跨轮追踪(第 5 节) | | **U 无法定类** | 证据不足或信号互相矛盾的显式未知 | 现场产物缺失无法复核;两类信号冲突 | — | 测试报告 §6 未闭环事项,人工判断;**不许硬编结论** | > 与既有三分法的关系:`api-testing` 运行纪律的"系统 Bug / 环境问题 / 用例自身错误"三分对应 > 本表的 A / C / B2——本规范是其全框架统一化,并补充了业界标配的 B1(预期变更)与 D(flaky)。 ## 3. 判定树(先全局,后个体) ```text 输入:失败集合 + 失败现场产物 第 0 步 · 全局扫描(C 类前置排除) │ 为什么先做:环境故障会污染整批判断——环境坏了之后个体分析全是噪音 ├─ 同批失败率 ≥50%,且错误形态趋同(同一 error type / 同一错误消息占比过半) │ 或登录态统一报错 / 目标服务不可达 / 5xx 密集 │ → 疑似单一环境事件:先定位它(部署中?账号锁定?数据污染?依赖挂了?) │ → 确认后整批标 C 待重验,直接跳第 4 步收尾 └─ 无全局形态 → 进入个体路径 第 0.5 步 · 个体聚簇(批量坍缩) │ 失败量大时先聚簇再定性:聚类键 = error message / 堆栈公共帧 / 选择器接口 │ (G 表「重复 error message」「错误密集形态」两组信号就是现成的聚类统计口径) ├─ 每簇只取证据最全的一条为代表进第 1 步定性 └─ 簇内其余条目继承代表的类别,evidence 必须引用代表行编号(零依据照抄视为无效行) 第 1 步 · 个体原样重跑(定性手段,不是通过手段;仅对簇代表执行) ├─ 稳定复现 → 进第 2 步 └─ 重跑通过 → D 候选(对照历史分流表翻灯记录可强化判 D,见第 4 节 G 表) 第 2 步 · A / B 区分(仅对稳定复现者) │ 该失败行为落在本次变更基线内吗?(git diff / 近期需求变更关联) ├─ 变更基线内 + 变更属预期(需求文档佐证或用户裁决)→ B1 ├─ 变更基线内但变更不属预期 / 实现未变而结果变了 → A ├─ 疑似用例自身错误(选择器永久失效 / 断言明显抄错)→ B2 └─ 分歧拿不准 → ⏸ 呈现双方证据等用户裁决(意图问题: 用户答复具有最终裁决力,evidence.md 三类裁决规则第三条) 第 3 步 · 收尾 └─ 以上全部未定 → U,进报告 §6 未闭环 第 4 步 · C 类收尾(反吞没条款二) 环境修复后,受影响批次原样重跑 ├─ 全部通过 → 该批次 resolved,事件单条归档 └─ 仍有失败 → 失败者退出 C,回第 1 步重新个体定性 (防止环境恢复把混入的真缺陷一并静默吞掉) ``` ## 4. G/S 双级信号 > 口径声明:本节 G/S 与类型决策矩阵(`test-type-matrix.md`)同名同哲学,但**信号源不同**—— > 矩阵扫的是被测仓库代码(scan_signals.py),本表读的是**执行产物**(results.xml / 错误文本 / > trace)。两者互不替代。 **G 级(机械可提取,聚合统计即可,无需 agent 语义理解):** | G 信号 | 提取来源 | 分类倾向 | |--------|---------|---------| | 同一 error message/type 重复 ≥3 条 | JUnit results.xml / 控制台文本聚合 | C | | `HTTP 5xx` / `net::ERR_*` / `ERR_CONNECTION_REFUSED` / 超时类密集 | 错误文本分类统计 | C | | 失败时间戳同秒级聚集 | results.xml timestamp 字段 | C | | 登录/鉴权 helper 统一栈帧报错 | 失败堆栈公共帧比对 | C | | assertion diff 中 expected = 文案/展示值,且近期有相关需求 commit | message 字段 × git log 对照 | B1(待补变更依据) | | 本轮红上轮绿交替 / 条目曾标 fixme | grep 历史分流文件 | D | | 选择器/元素不存在类错误且页面近期重构 | 失败消息 × git log | B2(待 S 级确认页面确已改版) | **S 级(agent 读 trace / 截图 / API 日志做语义复核):** - 截图核对断言对象的实际语义:是时序未完成、还是数据真的错、还是页面结构确实变了 - API 响应体逐字段核对是否符合新需求(B1 变更依据的具体佐证) - 自建数据残留检查(前序用例清理不彻底是 D 的常见根因) - 用例步骤与实际页面动线比对(发现 B2 写错点) **铁律:倾向 ≠ 结论。** G 级命中只产生预填候选;A 类定类必须过 S 级复核(E3 + 复核通过才允许记 Bug 条目);B1 没有 G 表以外的独立变更依据不得判 B(见下节条款一)。这与矩阵 exclude 永不预填 是同一防橡皮图章原则。 ## 5. 裁决规则与两条反吞没条款 - **A/B 分歧是意图问题**:按 `evidence.md` 第三类裁决规则,用户在澄清环节的明确答复有最终裁决力, 后续阶段不得推翻;分歧呈现时给双方证据摘要,不自答(对齐 `qa` 编排 ⏸ Bug 定性检查点)。 - **条款一(防免检通道吞真 Bug)**:判 B1 必须给出具体变更依据——commit hash、需求文档章节、或 用户裁决记录三者之一。给不出的一律回 A/U 处理。"看起来像需求变更了"不是依据。一次错误的 B 判定 会让真缺陷以资产更新为名合法消失——这与"基准答案标错、整条质量链跟着走偏"是同一类方向性风险。 - **条款二(防环境恢复掩盖缺陷)**:C 类不是丢进回收站——环境修复后受影响批次强制原样重跑, 仍失败者升 A 候选重新进判定树。禁止只说"环境问题已解决"就关闭整批。 - **D 类升级机制**:同一 TC 在历史分流文件中出现 ≥3 轮(`grep -l` 即可检索,这正是追加式留存的 目的)→ 升级处置优先级:fixme 转为排期修复根因,不允许永久 fixme 化(覆盖虚减风险,口径同 case-format 第 10 节对废弃用例的处理——可以废弃但必须留痕记数,不能静默消失)。 ## 6. 产出物模板:失败分流表 落盘 `{项目}/失败分流_{日期}.md`,结论直接被下游引用(报告 §2 统计口径不变,§3 只收 A 类条目): ```markdown # 失败分流 — {日期} - 来源:{哪轮执行:回归清单_20260827 / CI 夜间 full-run} - 规模:{总数} 失败 {失败数}(分流阈值触发原因:≥3 条) - 结论分布:A={n} B1={n} B2={n} C={n} D={n} U={n} ## 环境事件(C 类汇总,无则省略本节) - EVT-1:{现象}|定位:{单一根因}|处置:{修复动作}|重验:{批次与结果} ## 分流明细 | # | TC | 类别 | G 信号摘要 | S 级复核 | evidence | status | 去向 | |---|----|------|-----------|---------|----------|--------|------| | 1 | TC-02-03 | A | 断言 stable diff,无聚集 | 截图核对:优惠金额确实双扣 | level: E3, source: bug-001-02.png | fact | 报告§3 BUG-001 → bug-analysis 五字段 TODO | | 2 | TC-04-01 | B1 | expected=新版文案;关联 commit a1b2c3 | 响应体字段与新需求一致 | level: E4, source: report.txt [5] + 需求 v2.3 §4.2(运行 + 文档双来源印证) | fact | test-case-writing 增量修订(附依据) | | 3 | TC-05-02 | U | 无聚集 | trace 缺失无法复核 | level: E0, source: 仅用户口述 | hypothesis | 报告§6 未闭环,找{谁}要 {什么} | ``` - evidence 字段格式遵从 `evidence.md` 第 4 节;status 取值同其第 3 节(fact / inference / hypothesis…)——**没有证据支撑的分类行视为无效行** - C 类事件单列汇总而非按 TC 展开:环境故障是一因多果,逐 TC 重复记录只会制造噪音 - 大批量轮次给簇编组号(CL-1 / CL-2…):同簇继承行的类别照抄代表不算违规,但 **evidence 必须填「CL-x 代表 #n」格式的引用**——找不到所引代表行的继承行,同样按无效行处理 ## 7. 加载约定与其他 skill 的边界 **加载时机**(消费方在工作流中的标注方式,遵循 core 引用双形态约定): | 消费方 | 挂载点 | 时机标注 | |--------|--------|---------| | `automated-e2e-testing` | 工作流二之前 | 一轮执行结束失败 ≥3 条时(此时加载本文件),A 类才进证据收集与报告条目 | | `api-testing` | §4 运行与结果 | 失败 ≥3 条时改走批量分流,替换单条"先分辨"流程 | | `regression-testing` | §4 结果衔接 | 回归多失败先分流再同步 Bug 状态,防把 B/C/D 误刷成复验轮次 +1 | **边界速查:** | 场景 | 归属 | |------|------| | 执行中发现单条失败的即时定性(first-run-flaky 判定、等待策略) | 各执行 skill 工程约定 | | 一批失败的首轮分类与路由 | 本文件 | | 已确认 Bug 的根因 / 影响 / Severity / 修复建议 / 回归建议 | `bug-analysis` | | 修复合入后该跑哪些回归用例 | `regression-testing` | | B1/B2 导致的用例与脚本修订 | `test-case-writing` / 执行 skill 的资产维护流程 | | 类型域专项外部工具结果的回收 | 报告 §7(handoff 协议),与本文件无关 | ## 8. Common Mistakes | 错误 | 后果 | 正确做法 | |------|------|---------| | 每条失败都当 A 记 Bug | 误报淹没报告,开发信任崩塌,triage 声誉归零 | 先全局扫描再个体定性;A 过 S 级复核才落条目 | | 重跑变绿就放过(不标 D 不追踪) | flaky 混入绿灯名单,覆盖虚减,真回归夹杂其中随机漏网 | D 进分流表跨轮追踪;≥3 轮升级修根因 | | 判 B1 却给不出变更依据 | 免检通道吞掉真缺陷,且以"资产更新"合法消失 | 条款一:无依据一律回 A/U;问用户 | | C 类说一句"环境问题"就关整批 | 环境恢复后混入的真缺陷被静默吞掉 | 条款二:受影响批次强制原样重跑,仍失败者重新定性 | | 把 G 级命中直接当结论报出 | 机械预判成为橡皮图章 | 倾向≠结论;A 必过 S 级复核(同矩阵 exclude 永不预填) | | 分流行不给 evidence/status | 不可复核、不可续跑,后续阶段无从消费 | 无证据的分类行视为无效行 | | U 类硬编一个类别交差 | 显式未知变隐性误导,人工兜底线索丢失 | U 进报告 §6 未闭环,注明缺什么证据、找谁要 | | 大批量失败逐条独立定性 | 夜间数十条重复劳动到天亮,同类条目结论还互相矛盾 | 第 0.5 步先聚簇坍缩:每簇只定性代表,其余继承并引代表行编号 |
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.