Fixtures 与测试设计
# Fixtures 与测试设计
修订记录
| 版本 | 日期 | 修订说明 |
|---|---|---|
| v0.1 | 2026-05-01 | 建立当前设计基线;延续 kunora-wiki 作为 llm-wiki 方案的既有产品封板和工程设计,不推倒重来。 |
1. 文档目标
本文定义 kunora-wiki 自研模 块共享的测试夹具、契约测试和 golden tests 规则。
它的目标是让公共契约在实现前具备可验证标准:不同模块、不同实现者、不同 agent 不能各自解释路径、hash、manifest、错误码、状态流转和 API 响应结构。
本文不创建具体测试代码,也不规定测试框架。测试框架属于实现阶段选择;fixture 结构、覆盖范围和验收语义属于 common 契约,必须先稳定。
2. 设计目标
- 用 fixture 固化跨模块输入输出,避免实现阶段字段漂移。
- 用 contract test 验证 producer 和 consumer 对同一契约的理解一致。
- 用 golden test 验证路径、hash、id、manifest、错误对象等稳定输出不会被无意改动。
- 用 negative fixture 固化拒绝条件,确保非法输入不会进入正式内容、索引或 agent 写入链路。
- 用模块准入矩阵约束每个自研模块在进入实现前必须覆盖的公共测试类型。
3. 非目标
- 不规定单元测试框架、mock 框架、CI 平台或测试运行命令。
- 不覆盖 GitHub、Docusaurus、RAGFlow、Meilisearch 的常规部署可用性测试。
- 不把外部产品的私有返回结构作为 golden 输出。
- 不用 snapshot 代替契约定义;snapshot 只能验证已定义契约。
- 不要求 MVP 阶段一次性覆盖性能、压力和混沌测试。
4. Fixture 目录结构
后续实现阶段建议在仓库根目录创建 fixtures/common/**,用于保存公共契约样例。目录规划如下:
fixtures/
common/
path/
hash/
id/
config/
workspace/
sync/
review/
manifest/
index/
answer/
access/
agent/
error/
| 目录 | 覆盖内容 | 主要消费者 |
|---|---|---|
path/ | 路径规范化、非法路径、URL/anchor 生成 | WorkspaceStore、Display Adapter、Index Adapter、Answer API |
hash/ | Markdown 规范化、contentHash、hash 格式 | WorkspaceStore、SyncEngine、Index Adapter |
id/ | documentId、chunkId、runId、dedupeKey、idempotencyKey | 全部自研模块 |
config/ | source、publish、index、agent access 配置 | ConfigManager、SyncEngine、Index Adapter、Agent Access API |
workspace/ | DocumentRecord、文件状态、manifest 输入 | WorkspaceStore、Display Adapter、Index Adapter |
sync/ | SyncChange、SourceChanges、冲突和删除 | SyncEngine、ReviewBridge |
review/ | review report、check report、PR payload 标准结构 | ReviewBridge、Agent Access API、AgentBridge |
manifest/ | page、publish、site、index manifest | Display Adapter、Index Adapter、Answer API |
index/ | IndexDocument、SearchResult、部分失败 | Index Adapter、Answer API |
answer/ | AnswerRequest、AnswerResult、citation、无答案 | Answer API、Agent Access API |
access/ | tool request、scope、policy 拒绝、幂等反馈 | Agent Access API、AgentBridge |
agent/ | iteration task、RAGFlow 输出、agent report | AgentBridge、ReviewBridge |
error/ | ErrorObject、状态传播、retryable 标记 | 全部自研模块 |
5. Fixture 命名规则
Fixture 文件名必须表达场景和预期结果,不使用 test1、sample、mock 这类不可审查命名。
| 类型 | 命名规则 | 示例 |
|---|---|---|
| 正常输入 | <domain>.<scenario>.valid.<ext> | source.single.valid.yaml |
| 非法输入 | <domain>.<reason>.invalid.<ext> | path.absolute.invalid.json |
| 预期输出 | <domain>.<scenario>.expected.<ext> | hash.markdown-lf.expected.json |
| golden 输出 | <domain>.<scenario>.golden.<ext> | manifest.publish-basic.golden.json |
| 回归用例 | <domain>.<issue-or-case>.regression.<ext> | sync.case-conflict-delete.regression.json |
Fixture 内容必须满足:
- 每个 fixture 顶层包含
fixtureVersion、caseId、description、input、expected。 caseId在fixtures/common下唯一。- 预期失败 fixture 必须写明
expected.error.code和expected.error.retryable。 - 涉及时间的 fixture 必须使用固定时间,不使用当前时间。
- 涉及随机 id 的 fixture 必须使用固定 seed 或显式 expected id。
- 涉及外部产品的 fixture 只能保存 adapter 标准结构,不能保存供应商私有完整响应。
6. Contract Test 类型
Contract test 验证模块间契约,不验证模块内部实现细节。
| 测试类型 | 目的 | 必须验证 |
|---|---|---|
| Schema contract | 对象字段、必填项、枚举、类型和版本 | 缺字段拒绝、未知字段策略、版本兼容 |
| Producer contract | producer 输出是否满足公共结构 | 输出字段、状态、错误对象、幂等 key |
| Consumer contract | consumer 是否只依赖公共字段 | 不读取私有字段、不假设外部产品结构 |
| Adapter contract | 外部产品响应是否被转换为标准结构 | 错误映射、重试标记、降级输出 |
| Persistence contract | 写入 Git 或状态文件的结构是否稳定 | canonical JSON、manifest 字段、hash/id 稳定性 |
| Idempotency contract | 重试是否不会产生重复副作用 | 相同 key 返 回同一结果或安全 no-op |
Contract test 必须优先覆盖 Public Contract,其次覆盖跨两个以上模块共享的 Internal Contract。
7. Golden Test 类型
Golden test 用于稳定确定性输出。任何 golden 更新都必须被当作契约变更审查,不能在失败后无解释刷新。
| Golden 类型 | 输入 | 输出 | 更新要求 |
|---|---|---|---|
| Path golden | 原始路径、source id | normalized path、publish path、anchor | 需要说明路径规则变更原因 |
| Hash golden | Markdown 文本 | contentHash、canonical JSON hash | 需要说明规范化规则变更原因 |
| ID golden | path、hash、caller、task 输入 | document/chunk/run/dedupe/idempotency id | 需要说明稳定键变更影响 |
| Manifest golden | DocumentRecord 列表 | page/publish/site/index manifest | 需要说明下游消费影响 |
| Error golden | 失败输入 | ErrorObject 和 propagated status | 需要说明调用方处理影响 |
| Answer golden | search result/context pack | answer result、citation、no-answer | 需要说明用户可见行为影响 |
Golden test 的输出文件必须使用稳定排序、稳定缩进和稳定字段顺序,避免无意义 diff。
8. 必备 Fixture 清单
8.1 路径、hash 与 id
| Fixture | 场景 | 预期 |
|---|---|---|
fixtures/common/path/valid-publish-paths.json | 合法 publish path 集合 | 全部规范化为 / 分隔相对路径 |
fixtures/common/path/invalid-paths.json | 绝对路径、..、空 segment、保留目录越界 | 返回 path.invalid 或更具体错误 |
fixtures/common/hash/markdown-lf.md | LF Markdown | 生成稳定 contentHash |
fixtures/common/hash/markdown-crlf.md | CRLF Markdown | 与 LF 等价内容生成相同 contentHash |
fixtures/common/hash/markdown-bom.md | 带 BOM Markdown | BOM 不影响 contentHash |
fixtures/common/id/document-id-stable.json | 同一路径不同内容 | documentId 不变,contentHash 变化 |
fixtures/common/id/idempotency-by-caller.json | 不同 caller 相同操作 | idempotencyKey 不同 |
8.2 配置与工作区
| Fixture | 场景 | 预期 |
|---|---|---|
fixtures/common/config/sources.single.valid.yaml | 单 source 配置 | 生成 SourceConfig 和 ConfigBundle |
fixtures/common/config/sources.duplicate-publish-path.invalid.yaml | 两个 source 写入同一 publish path | 返回 config.duplicate_path |
fixtures/common/config/index.disabled.valid.yaml | 索引关闭 | Index Adapter 不执行 upsert |
fixtures/common/workspace/document-record.valid.json | 正常文档记录 | hash、path、status 完整 |
fixtures/common/workspace/document-record-missing-hash.invalid.json | 缺少 contentHash | schema contract 拒绝 |
8.3 同步、审核与 manifest
| Fixture | 场景 | 预期 |
|---|---|---|
fixtures/common/sync/add-update-keep-conflict.json | 新增、更新、保持、冲突混合 | 输出稳定 SourceChanges |
fixtures/common/sync/delete-source-page.json | 上游删除页面 | 输出 delete change,不直接删除正式内容 |
fixtures/common/review/check-report-blocking.json | 阻断性检查失败 | ReviewBridge 生成 failed check |
fixtures/common/manifest/publish-manifest.valid.json | 基础发布 manifest | canonical JSON 稳定 |
fixtures/common/manifest/site-manifest-build-failed.json | 展示构建失败 | 不生成 success site manifest |
fixtures/common/manifest/index-manifest.partial.json | 部分索引失败 | 成功项和失败项可区分 |
8.4 索引、问答、访问与 agent
| Fixture | 场景 | 预期 |
|---|---|---|
fixtures/common/index/upsert-delete-idempotent.json | 重复 upsert/delete | 无重复副作用 |
fixtures/common/index/search-result-citation.json | 搜索结果带引用 | Answer API 可生成标准 citation |
fixtures/common/answer/no-answer.json | 检索不足或置信度不足 | 返回 answer.no_answer,通常 HTTP 200 |
fixtures/common/answer/citation-path-anchor.json | 引用包含 path 和 anchor | citation 可回到展示页面 |
fixtures/common/access/forbidden-scope.json | caller 请求未授权 scope | 返回 access.forbidden,不调用下游 |
fixtures/common/access/feedback-dedupe.json | 重复反馈 | 使用 dedupeKey 合并或安全 no-op |
fixtures/common/agent/concurrent-change.json | agent 基于过期内容生成 diff | 返回冲突,不覆盖正式内容 |
fixtures/common/agent/ragflow-invalid-output.json | RAGFlow 输出缺字段 | AgentBridge 拒绝创建 PR |
9. 错误与状态测试要求
错误测试必须覆盖错误对象、状态传播和降级语义。
| 场景 | 输入 | 预期 |
|---|---|---|
| 配置错误 | 无 效 source schema | config.invalid_schema,retryable=false |
| 路径越界 | ../publish/a.md | path.traversal 或 path.invalid,不读写文件 |
| Git 写入冲突 | base revision 过期 | workspace.conflict,可由人工或重试解决 |
| 展示构建失败 | Docusaurus build adapter 返回失败 | page/publish 成功状态不被错误标记为 site 成功 |
| 索引部分失败 | 部分文档 upsert 失败 | index manifest 标记 partial,不回滚 Git 内容 |
| RAGFlow 不可用 | answer 或 iteration 调用失败 | 不编造答案,不创建改进 PR |
| 权限不足 | caller scope 不满足 policy | access.forbidden,不泄漏受限内容 |
所有错误测试必须断言:
code使用<domain>.<reason>。severity与调用方处理方式一致。retryable不被默认设为 true。module和operation可用于审计定位。details不包含密钥、token、完整私有配置或未授权内容。
10. 模块准入矩阵
模块进入实现前,必须声明自己消费和生产哪些 fixture,并满足下表最低测试要求。
| 模块 | 必须通过的 common 测试 |
|---|---|
| ConfigManager | config schema、config invalid、config impact、error object |
| WorkspaceStore | path、hash、id、document record、manifest persistence、workspace conflict |
| SyncEngine | config input、source changes、sync conflict、delete semantics、idempotency |
| ReviewBridge | review report、check report、blocking status、PR payload adapter contract |
| Display Adapter | page manifest、site manifest、path/anchor、build failure degradation |
| Index Adapter | index document、index manifest、upsert/delete idempotency、partial failure |
| Answer API | answer request/result、search result、citation、no-answer、index unavailable |
| Agent Access API | scope、policy、tool request/result、feedback dedupe、audit event |
| AgentBridge | iteration task、RAGFlow output validation、concurrent change、PR report |
如果模块不满足对应 common 测试,模块设计文档必须显式记录延期原因和补齐计划。
11. CI 与本地检查要求
实现阶段的 CI 至少应分三层:
| 层级 | 运行时机 | 内容 |
|---|---|---|
| Fast contract checks | 每次提交或 PR | schema、path/hash/id、fixture parse、基础 golden |
| Module contract checks | 模块变更 PR | producer/consumer contract、adapter mock、错误状态传播 |
| Integration smoke checks | 合并前或 nightly | 从 source 到 manifest/index/answer 的最小链路 |
本地检查应支持只运行 common fixture,以便在模块实现前先验证公共契约没有被破坏。
CI 失败处理规则:
- Contract test 失败默认阻断合并。
- Golden test 失败必须人工确认是否为预期契约变更。
- Negative fixture 失败必须阻断合并,因为它代表非法输入被接受。
- 外部产品不可用导致的 adapter 测试失败,应区分 mock contract 失败与真实环境不可用。
12. 契约稳定完成标准
development/common 的 fixture 与测试契约达到稳定状态,需要满足:
- Public Contract 都至少有一个 valid fixture 和一个 invalid fixture。
- 跨两个以上模块共享的 Internal Contract 都至少有 schema contract。
- 路径、hash、id、manifest 和错误对象都有 golden test。
- 每个自研模块在自己的设计文档中引用对应 common fixture 范围。
- Golden 输出变更有 review 说明,不允许无说明批量刷新。
- Fixture 不包含真实密钥、真实用户隐私数据或外部产品私有完整响应。
13. 待实现事项
- 创建
fixtures/common/**实际目录和首批 fixture 文件。 - 为公共数据结构建立 schema 校验。
- 为 path/hash/id 规则建立 golden 输出。
- 为 Public Contract 建立 producer/consumer contract test。
- 在 CI 中增加 common fixture 检查入口。