17 KiB
17 KiB
NCurator-Desktop 测试架构
1. 目标
- 测试体系收敛为两层产物:
- 原始日志:
tests/logs/** - 正式分析文档:
tests/report/<domain>/report.md
- 原始日志:
- 原始日志统一由四类文件组成:
meta.jsontimeline.jsonlruntime-events.jsonlartifacts.json
- 测试样例只记录事实,不直接写失败原因分析、优化建议或能力判断。
2. 目录结构
tests/
harness/
fixtures/
ai/
backend/
tools/
terminal/
basic/
boundary/
scenario/
live/
docx/
basic/
boundary/
scenario/
live/
kb/
basic/
boundary/
scenario/
live/
human/
basic/
boundary/
scenario/
live/
director-intent/
basic/
boundary/
scenario/
live/
director-context/
basic/
boundary/
scenario/
live/
director-plan/
basic/
boundary/
scenario/
live/
entire/
basic/
boundary/
scenario/
live/
e2e/
workbench/
basic/
boundary/
scenario/
live/
kb-search/
backend/
basic/
boundary/
scenario/
live/
e2e/
basic/
boundary/
scenario/
live/
temp/
logs/
report/
ai/
report.md
规则:
- 目录统一使用 kebab-case。
- 测试发现目录使用通用树形结构:
tests/<domain>/<layer>/<suite>/<groups...>/<case>.test.ts。 suite是第一层稳定分类;groups...是 suite 下可选的任意深度子目录。- 正式测试目录默认优先使用四类稳定分组:
basic、boundary、scenario、live。 basic / boundary / scenario用于组织稳定回归基线;live用于组织真实模型路径验证。- 只要某个正式测试范围涉及模型参与,就必须在该范围下维护
live/目录和对应的 live case。 temp/默认不参与 gating,也不参与正式 report 汇总。- 除
tests/temp/**外,tests/**中的正式测试目录只允许放已在doc/test-plan/**登记的 case,否则需要主动讨论。
3. 测试用例设计原则
3.1 基础case
- 这类case要保证基础能力能正常使用。
- case 尽量短、输入尽量单一、断言聚焦核心结果。
3.2 边界case
- 这类case要覆盖失败、阻塞、歧义、安全限制、空结果、超时等边界情况的处理。
3.3 实际场景case
- 这类case是核心,要能直接对应真实用户诉求。
3.4 Live case
- 这类case用于验证真实模型参与下的正式执行路径。
- 只要生产路径中存在模型参与,就不能只靠
basic / boundary / scenario得出能力成立结论。 livecase 应优先依赖真实 fixture、真实流程和正式交付,而不是只验证脚本化骨架。- 对
human、长任务、端到端任务这类能力,live是主结论来源;非 live case 主要用于稳定回归与诊断。
3.5 长期价值
- 设计每个case时需考虑它的长期性,适合后续重复回归。
- 进入正式 domain 目录的 case 默认都应具备长期回归价值。
- 长期 case 应有稳定目标、稳定输入、稳定验收口径。
- 对涉及模型的长期 case,应规划对应的
live验证,不把“脚本化通过”当作最终完成。 - 所有长期维护的正式 case,必须先对齐
doc/test-plan/**下对应测试计划中的用例定义,再实现测试代码。 - 实现正式测试代码时,必须先对齐对应 case 的
What / Why / How / Oracle / Not,不能只按目录名、caseId或相邻样例自行脑补。 - 不允许绕过
test-plan直接新增正式测试用例代码;如果测试计划中还没有对应 case,必须先补文档,再落测试实现。 - 正式测试代码中的
caseId、目录归属、分组层级、核心验收口径,必须与test-plan中对应 case 保持一致。
3.6 临时case
- 一次性排查、临时验证或待定样例统一放在
tests/temp/**。 - 不要把临时用例混入正式 domain 目录。
- 临时 case 不参与正式 report,也不作为阶段完成依据。
- 未在
test-plan定义的验证,不得直接进入正式测试目录;需要先排查时只能放在tests/temp/**。 - 如果某条临时 case 需要转为长期维护 case,必须先补齐
doc/test-plan/**中的正式用例定义,再迁入正式测试目录。
4. runner 与命令
统一入口是 scripts/test-workflow.mjs。
当前命令面:
pnpm test:aipnpm test:ai:backendpnpm test:ai:backend:toolspnpm test:ai:e2epnpm test:ai:sample
runner 支持的过滤参数:
--domain--layer--suite--group,可重复传入--case--runs
补充规则:
- 默认按
suite运行时,runner 只收集非live/正式 case,避免把稳定回归和 live 验证混在同一批执行里。 livecase 应通过显式--group live或显式--case运行。
runner 的职责只有三件事:
- 找到目标测试文件并执行。
- 落原始日志到
tests/logs/**。 - 在 e2e 请求下先执行同域 backend。
runner 不负责:
- 写事实汇总稿
- 写失败原因分析
- 写优化建议
- 更新正式
report.md
5. 原始日志契约
每个 run 固定目录:
tests/logs/<domain>/<layer>/<suite>/<groups...>/<caseId>/<runId>/
meta.json
timeline.jsonl
runtime-events.jsonl
artifacts.json
artifacts/
其中:
meta.json- 单次 run 的摘要索引。
- 用来回答“本次 run 是谁、结果是什么、统计值是什么”。
timeline.jsonl- 单次 run 的事实事件流。
- 用来回答“本次 run 按顺序发生了什么”。
runtime-events.jsonl- 单次 run 的完整运行时原始事件流。
- 用来回答“底层 runtime 真实发出了哪些
sendEvent事件”。
artifacts.json- 单次 run 的附件索引。
- 用来回答“本次 run 产生了哪些可回看的附件”。
artifacts/- 可选目录。
- 只有确实生成 trace、截图、HTML 诊断页等实体附件时才创建。
目录与命名规则
<domain>/<layer>/<suite>/<groups...>- 目录层级体现业务域、测试层和 suite 树。
- 当前 runner 通过目录过滤测试范围。
<caseId>- 对应用例稳定标识。
- 通常与测试文件名一致。
<runId>- 单次执行唯一标识。
- 当前格式:
YYYYMMDD-HHmmss-SSS。
meta.json
固定字段:
schemaVersionrunIdcaseIdscopestatusstartedAtfinishedAtstatsruntime
字段含义:
schemaVersion- 日志 schema 版本号。
- 固定值为
4。
runId- 本次执行唯一标识,对应目录名。
caseId- 说明这次 run 属于哪条用例。
scope- 说明这次 run 属于哪个稳定测试范围。
- 格式为
<domain>/<layer>/<suite>/<groups...>,不包含caseId。
status- 本次 run 的最终结果。
- 当前值域是
passed/failed。
startedAt- 本次 run 的开始时间。
finishedAt- 本次 run 的结束时间。
stats- 本次 run 的结构化统计。
- 当前固定字段:
durationMsstepsassertionserrorsartifacts
runtime- 本次 run 的运行时摘要。
- 当前固定字段:
drivertargetmodelName
约束:
meta.json顶层只保留上述固定字段。- 如果测试需要保留 fixture 依赖信息,应写在
timeline.jsonl的run/start事件data中。
timeline.jsonl
固定字段:
tstypenamestatusidmessagedata
字段含义:
ts- 当前事件发生时间。
type- 事件大类。
- 当前值域:
run- 只用于单次 case 的运行生命周期。
step- 只用于测试作者显式定义的语义步骤,不用于底层工具噪音。
assertion- 只用于
Oracle或核心验收断言。
- 只用于
artifact- 只用于登记一个已经写出的附件。
signal- 只用于外部可观察信号,例如路由变化、IPC 调用、真实 runtime event、console。
error- 只用于 runner 或测试执行层面的未恢复错误。
name- 事件的稳定语义名,不是脚本名。
- 同类事实在不同 run、不同 case 中应尽量复用同一命名。
status- 当前事件状态。
- 当前值域:
infookfailedskipped
id- 可选稳定事件标识。
- 当前主要用于
step和assertion,便于关联截图等附件。
message- 当前事件的一句事实性自然语言。
- 只写“发生了什么”,不写失败原因、优化建议或能力判断。
data- 结构化补充信息,可空。
- 只放按需原始观测值,不要求统一业务 schema。
约束:
timeline.jsonl只记录高价值语义事件,不记录底层噪音。signal用于记录可观察到的外部信号,例如路由变化、IPC 调用、AI 事件。- 当失败事件存在明确证据时,优先把证据放进
data或 artifact,不要在message中写大段解释。 - e2e 下不把所有 Playwright 底层调用逐条倾倒进 timeline;底层重放依赖
trace.zip,timeline 只保留语义 step / assertion / signal。 run之外的业务状态不要塞进status。- 例如
blocked、completed、cancelled应保留在runtime-events.jsonl或写进data,不要扩充timeline.status。
- 例如
runtime-events.jsonl
- 文件内容是逐行
AiRunEventJSON,不包额外 wrapper,不做 schema 投影。 - 范围固定为完整
sendEvent原始事件流。- 包括 tool、guard、scope 校验、审批、产物、runtime issue、AI 运行等所有通过
sendEvent发出的事件。 - 不包含本地 harness 自己维护但未经过
sendEvent的临时数组,例如某些工具测试里的activities[]。
- 包括 tool、guard、scope 校验、审批、产物、runtime issue、AI 运行等所有通过
runtime-events.jsonl始终写出;如果本次 run 没有 runtime 事件,则写空文件。- backend 与 e2e 的采集路径可以不同,但最终落盘契约必须一致。
- 当
Oracle、断言data、timeline.jsonl或 report 仍不足以解释失败原因时,必须继续回看runtime-events.jsonl。
artifacts.json
固定字段:
schemaVersionitems
其中 items 每项固定字段:
idkindpathrefs
字段含义:
schemaVersion- 附件索引 schema 版本号。
- 固定值为
4。
id- 单个附件的稳定标识。
kind- 附件类型。
- 当前值域:
tracescreenshotvideoplaywright-reportjsonlother
path- 相对当前 run 根目录的稳定路径。
refs- 可选关联索引。
- 当前字段:
stepIdassertionId
约束:
artifacts.json必须始终写出,即使items为空数组。artifacts/目录只有在确实存在实体附件文件时才创建。
6. backend 与 e2e 的统一约束
- backend 和 e2e 共用同一份
meta.json/timeline.jsonl/runtime-events.jsonl/artifacts.json基础契约。 - backend 与 e2e 的差异只体现在:
meta.runtime.drivermeta.runtime.targetartifacts.json.items[].kindruntime-events.jsonl的采集路径timeline.jsonl中出现的语义事件名与信号类型
e2e 默认附件策略
- 成功 run:
- 始终保留 Playwright trace。
- 只有测试或 runtime 显式要求保留的关键截图才写入。
- 默认不生成 Playwright HTML 诊断页。
- 失败 run:
- 始终保留 Playwright trace。
- 始终生成失败截图,并通过
refs.assertionId或refs.stepId绑定。 - 生成位于
artifacts/playwright/report/index.html的 HTML 诊断页。
- 默认不保留 video。
7. 正式 report
当前正式分析文档固定放在:
tests/report/<domain>/
report.md
含义:
report.md- 当前业务域唯一正式分析报告。
- 是基于
tests/logs/<domain>/**写出的当前现状分析。
report.md 建议结构
总览总览至少包含:分析范围总体结论下一阶段建议
- 按
backend/e2e展开 - 每个 layer / suite / groups 节点按需要继续下钻
- 每个节点至少包含:
证据通过率失败分析当前评估下一步建议
report.md 格式约束
- 顶层标题使用
# <Domain> 域测试报告。 - 顶层必须先有
## 总览。 总览下必须先包含以下固定小节,顺序保持一致:### 分析范围### 总体结论### 下一阶段建议
总体结论中的统计口径固定拆成两类:历史总数据:基于当前 analysis window 内全部 raw runs 统计最新总数据:每个 case 只看最新一轮 run 的修复后统计
总览之后按 domain 下实际存在的 layer 展开,当前通常是## backend与## e2e。- layer 之下按目录树继续展开:
- suite 使用
### - suite 下第一层 group 使用
#### - 更深层 group 继续按 Markdown 标题层级递增
- suite 使用
- 每个实际分析节点都必须包含以下固定小节,顺序保持一致:
证据通过率失败分析当前评估下一步建议
证据默认只列出能定位该条证据的最小信息,例如caseId、runId、status。- 不要在 report 正文里机械展开
meta.json、timeline.jsonl、runtime-events.jsonl、artifacts.json这些文件名。 - 只有当失败分析或结论明确依赖某个关键附件时,才额外补充附件名称或用途说明。
通过率只写当前节点覆盖范围内的统计,不混入其他节点数据。通过率固定拆成两组:历史总数据:totalRuns、passedRuns、failedRuns、historicalSuccessRate最新总数据:latestCaseCount、latestPassedCases、latestFailedCases、latestSuccessRate
历史总数据与最新总数据不得混写成同一个 successRate 口径。失败分析在没有 failed run 时应明确写“当前没有 failed run,因此无法给出失败模式结论”。当前评估负责总结当前状态和主要不足。下一步建议只写基于现有证据能支撑的下一步建议。
report.md 最小示例
# AI 域测试报告
## 总览
### 分析范围
- raw logs 范围: 当前仓库内 `tests/logs/ai/**`
- 已分析 scope: `ai/backend/tools/terminal`、`ai/e2e/workbench`
### 总体结论
- 历史总数据: `2` 次 raw runs,其中 `2` 次 passed、`0` 次 failed,historicalSuccessRate=`100.0%`
- 最新总数据: `2 / 2` 个 case 的最新一轮 run 为 passed,latestSuccessRate=`100.0%`
- 当前结论: 当前 backend sample 与 e2e smoke 已跑通,但失败路径覆盖仍不足。
### 下一阶段建议
- 先补 `backend/tools/terminal` 的失败路径
- 再补 `e2e/workbench` 的阻塞分支
## backend
### tools
#### terminal
##### 证据
- `terminal-basic-command` | run=`20260315-101530-112` | status=`passed`
##### 通过率
- 历史总数据:
- totalRuns: 1
- passedRuns: 1
- failedRuns: 0
- historicalSuccessRate: 100.0%
- 最新总数据:
- latestCaseCount: 1
- latestPassedCases: 1
- latestFailedCases: 0
- latestSuccessRate: 100.0%
##### 失败分析
- 当前没有 failed run,因此无法给出失败模式结论。
##### 当前评估
- terminal happy path 已稳定产出 raw logs。
- 当前不足是还没有正式纳入失败/阻塞样例。
##### 下一步建议
- 增加 `workspace-boundary-rejected`
- 增加 `approval-denied`
## e2e
### workbench
##### 证据
- `workbench-plan-artifact-smoke` | run=`20260315-103002-008` | status=`passed`
- 关键附件: playwright trace
##### 通过率
- 历史总数据:
- totalRuns: 1
- passedRuns: 1
- failedRuns: 0
- historicalSuccessRate: 100.0%
- 最新总数据:
- latestCaseCount: 1
- latestPassedCases: 1
- latestFailedCases: 0
- latestSuccessRate: 100.0%
##### 失败分析
- 当前没有 failed run,因此无法给出失败模式结论。
##### 当前评估
- workbench smoke 主链路已可用。
- 当前不足是 approval、workspace required、review 等真实路径尚未纳入。
##### 下一步建议
- 补充 approval 路径
- 补充 workspace required 场景
约束:
- report 只引用 raw logs 与 artifacts 作为证据来源。
- report 可以做分析与建议,但不得写入没有日志依据的推断。
8. Codex 后置分析工作流
当用户要求 Codex 跑测试并继续分析时,Codex 必须按以下顺序执行:
- 运行目标测试命令。
- 读取对应 domain 下的 raw logs。
- 先看
timeline.jsonl;若信息仍不足,再回看runtime-events.jsonl与关键附件。 - 必要时继续回看失败 run 的
meta.json与artifacts.json。 - 更新
tests/report/<domain>/report.md。
9. 明确禁止
- 测试样例直接输出失败原因分析。
- runner 自动调用测试所用 AI 模型生成 report。
- report 写入没有日志依据的推断。
- 为单个 runner 或单种工具单独再设计一套平行 raw log 契约。