# 自动测试说明

## 1. 目标

这套测试只做一件事：

- 用固定配置初始化测试环境；
- 一键运行测试；
- 验证同步系统能不能完成一轮正常同步；
- 验证常见的更新接口能不能工作；
- 能把问题大致分成三类：数据问题、推送系统问题、后端接口问题。

不追求把所有情况都测完，也不追求覆盖所有脏数据。测试深度控制在：

- 能跑通一次正常流程；
- 能验证一批关键 update；
- 能处理一些基本异常；
- 每轮都能留下清楚的报告，供下一轮继续改。

## 2. 测试系统的组成

测试系统由 4 部分组成：

1. **测试配置**
2. **初始化脚本**
3. **按 domain 拆分的用例**
4. **统一运行脚本**

只要把这 4 部分准备好，后面就应该做到：**运行一个脚本，就自动完成初始化、执行、分析、出报告。**

### 当前已落地的文件

目前已经先实现了一版最小骨架：

- 测试总配置：[tests/fixtures/auto_sync/auto_test_config.yaml](../../tests/fixtures/auto_sync/auto_test_config.yaml)
- 流水线测试配置：[run_profiles/jsonl_to_api.test.yaml](../../run_profiles/jsonl_to_api.test.yaml)
- 初始化脚本：[scripts/auto_test_init.py](../../scripts/auto_test_init.py)
- 统一运行脚本：[scripts/run_auto_test.py](../../scripts/run_auto_test.py)
- 公共工具：[scripts/auto_test_lib.py](../../scripts/auto_test_lib.py)
- 第一个 domain 用例：[tests/fixtures/auto_sync/domains/contract.yaml](../../tests/fixtures/auto_sync/domains/contract.yaml)

## 3. 配置管理

测试环境必须基于配置初始化，不能靠手工改路径。

配置里至少要能定义这些内容：

- 后端代码目录；
- 后端启动命令；
- 后端恢复数据库命令；
- 测试使用的数据库名；
- CouchDB 连接信息；
- 本地同步状态数据库路径；
- 基础种子数据目录；
- 同步测试数据目录；
- 临时测试数据目录；
- 同步系统运行配置文件路径。

建议单独准备一个测试配置，例如：

- `run_profiles/jsonl_to_api.test.yaml`
- `docs/auto_test_spec` 中再约定测试总配置文件

### 数据目录分工

测试里至少有 3 类数据目录：

1. **基础种子数据**
   - 用来恢复后端里那些不能通过同步创建的数据。
   - 例如：`company`、`project`、`design_doc`。
   - 这类数据先进入后端数据库，给后续同步提供依赖。

2. **同步测试数据**
   - 用来测试可创建、可更新的业务数据。
   - 例如合同、结算、施工任务等。

3. **临时运行目录**
   - 每次测试前，把测试数据复制到这里。
   - 后续每一轮如果要改数据，也只改这里，不改原始测试集。

## 4. 初始化方式

初始化必须自动完成，不能再靠手工执行几条命令。

目标流程如下：

1. 读取测试配置；
2. 如有旧测试数据库，先自动删除；
3. 自动删除本地同步状态数据库；
4. 自动执行后端的数据库恢复命令，导入基础种子数据；
5. 自动启动后端；
6. 自动等待后端 ready；
7. 自动准备临时测试目录；
8. 开始跑测试。

### 运行后的要求

- **测试跑完以后，数据库保留**，方便查看结果；
- **下次再运行时，再自动清空旧库并重建**；
- 不要求每次结束自动销毁环境。

## 5. 运行方式

不强制使用 `pytest`。

当前更适合的方式是：

- 用一个主脚本统一运行全部流程；
- domain 用例由脚本按顺序调度；
- 每个 domain 可以包含多轮 pipeline；
- 每轮的输入数据和检查点都由脚本控制。

也就是：

- `测试配置 + 初始化脚本 + 分 domain 的用例 + 一个完整运行脚本`

## 6. 测试轮次

一次完整测试，允许分成多轮 pipeline。

典型做法：

### 第 1 轮：初始化后首次同步

目的：

- 验证基础依赖准备正确；
- 验证 create 流程能跑通；
- 验证绑定关系是否正常。

### 第 2 轮：在临时目录修改部分数据后再次同步

目的：

- 验证 update 接口；
- 验证哪些数据应该更新；
- 验证哪些数据应该保持不动。

### 第 3 轮：补充少量基本异常场景

目的：

- 验证系统遇到常见错误时能不能给出清楚结果；
- 验证错误是否能定位到数据、推送系统或后端。

不要求无限轮次，也不要求复杂压力测试。

## 7. Domain 用例怎么设计

每个 domain 单独设计用例，例如：

- `company`
- `project`
- `contract`
- `supplier`
- `construction_task`
- `contract_settlement`

但不是所有 domain 都从创建开始测。

要根据业务真实情况决定：

- 哪些 domain 只能作为基础种子存在；
- 哪些 domain 可以通过同步创建；
- 哪些 domain 要重点测 update。

### Domain 用例必须写清楚的内容

每个 domain 的用例说明都要先写文字，再写代码。文字说明必须经过你审定。

每组用例至少写清楚：

1. 这个 domain 的前置依赖是什么；
2. 这个 domain 本轮准备哪些测试数据；
3. 第几轮会创建哪些记录；
4. 第几轮会修改哪些字段；
5. 预期通过什么方式判断结果；
6. 这个用例主要想识别哪类问题。

## 8. Domain 用例的测试原则

### 原则 1：优先覆盖大部分接口

目标不是追求完美覆盖，而是尽量覆盖大部分 create / update 接口，尤其是 update。

### 原则 2：只测明确的业务关系

用例里的依赖关系必须有文字说明，不能边跑边猜。

### 原则 3：可以通过改数据来构造用例

允许通过修改临时目录里的数据来构造 update 场景。

### 原则 4：判断结果不能只看日志

要结合下面几类证据一起判断：

- 日志；
- collection 中的数据；
- 持久化数据库中的结果；
- 绑定记录；
- 同步状态数据库。

### 原则 5：用例要能分出问题归属

每个用例都应该尽量帮助判断问题属于：

- 测试数据问题；
- 推送系统问题；
- 后端接口逻辑问题。

## 9. 建议的用例层次

每个 domain 的用例，优先按下面 4 类来补：

1. **正常创建**
2. **正常更新**
3. **关键绑定关系检查**
4. **基本异常检查**

这里的“基本异常”只做常见的、对当前系统最有价值的，例如：

- 缺少依赖；
- 关键字段不合法；
- 远端返回明确错误；
- 本地状态和远端状态不一致。

## 10. 运行脚本应该负责什么

统一运行脚本应负责：

1. 读取测试配置；
2. 清空旧测试环境；
3. 初始化后端数据库；
4. 启动后端并等待可用；
5. 准备临时测试数据；
6. 按 domain 顺序执行各轮用例；
7. 每轮运行 pipeline；
8. 收集结果；
9. 输出报告。

## 11. 测试报告要求

每轮都要有清晰报告，给下一轮继续优化使用。

报告至少包含：

- 测试时间；
- 使用的配置；
- 运行了哪些 domain；
- 每个 domain 跑了几轮；
- 每轮改了哪些数据；
- 每轮成功 / 失败统计；
- 失败样本；
- 问题分类：数据 / 推送系统 / 后端；
- 下一轮建议。

## 12. 当前结论

当前最合适的方案不是全量自动化，也不是一次性测完所有东西。

而是：

- 先做一套固定配置的测试环境；
- 用一个脚本跑完整个流程；
- 从少量 but 关键的 domain 开始；
- 每个 domain 先把 create 和主要 update 跑通；
- 每轮都留下明确报告；
- 再逐步扩展。

这样更容易维护，也更适合后续让 Agent 按计划继续写代码。

## 13. 当前执行计划（2026-03-07）

当前轮次先处理两类目标：

1. `project_detail_data`
   - 继续确认可按统一 `project_detail_data` 语义验证的 key 与相关 update 接口在测试报告中可见、可追踪；
   - 重点检查它与后续 project 级 domain 连跑时是否污染状态。
   - 当前水电项目下，`production` 先不混入这组统一回归：后端已确认该接口会直接更新 `project` 字段，但不保证创建/更新可轮询的 `project_detail_data.production` 记录；后续待业务确认后再单列测试方案。

2. 剩余 domain
   - 当前优先处理：`contract_settlement_detail`、`personnel`、`personnel_detail`
   - 已完成并保持回归：`preparation`、`production`、`contract`、`contract_settlement`、`construction_task`、`construction_task_detail`、`material`、`material_detail`
   - 之后再继续：`units`、`contract_change`、`lar`、`lar_change`

本轮执行顺序：

1. 先补齐文字测试计划；
2. 再修改测试脚本与 `tests/` 下测试数据；
3. 逐个 domain 跑通；
4. 最后回到整套自动测试顺序验证全量可跑通。

### 本轮强约束

- **禁止修改 `schemas/` 下任何文件。**
- 不允许为了让测试通过而绕过 schema 校验。
- 如果问题根因是后端接口/后端返回/后端数据模型不一致，应直接在报告里暴露并记录，不擅自改 schema。
- 只有在确认是后端错误并得到明确指示后，才讨论是否调整类型定义。

### 本轮排查重点

1. `project_detail_data` 单跑已基本可用，但要继续验证与 `preparation`、`production` 连跑时是否造成绑定或状态污染；
2. `preparation`、`production` 当前更像是“整套顺序执行时失败、单独执行可通过”的问题，优先检查运行顺序、临时数据、持久化状态和绑定残留；
3. 其余 domain 按“先单 domain 跑通，再放回全量顺序”推进；
   - 当前先按 `contract_settlement_detail -> personnel -> personnel_detail` 顺序落地；
4. 每修完一个 domain，都要保证报告中能看出本轮覆盖内容、绑定结果、字段变化和失败归因。
5. 对 `project_detail_data.production`，禁止再用“接口没报错就直接记成功”的短路逻辑掩盖真实语义；当前阶段直接绕过该 key 的统一回归，保留差异说明。

## 14. 大规模测试方法：按轮迭代，而不是一次堆上下文

大规模测试的目标不是“把所有失败细节一次讲完”，而是：

- 每一轮只有一个清晰基线；
- 每一轮只记录少量必须保留的信息；
- 每一轮都能看到总计变化；
- 每一轮都能区分“新问题、已修复问题、遗留问题”。

当前推荐固定采用 **单 domain 验证 + 全量聚合回归** 的两层节奏。

### 14.1 固定节奏

每一轮都按下面顺序执行：

1. **选定本轮目标**
   - 只选 1 个主问题，或者 1 组同类问题。
   - 例如：
     - 一个 domain 的 update 接口失败；
     - 一组共享 helper 导致的脚本设计问题；
     - 一类后端读写不一致问题。

2. **先单 domain 复现**
   - 用 [scripts/run_auto_test.py](../../scripts/run_auto_test.py) 只跑目标 domain。
   - 目的不是求“全绿”，而是先确认问题是否稳定复现。

3. **定位并修复一类根因**
   - 如果是测试脚本问题，就只修脚本；
   - 如果是后端问题，就在报告里明确标注为后端；
   - 如果是数据问题，就保留失败，不用 fallback 掩盖。

4. **回跑目标 domain**
   - 只看本 domain 是否改善；
   - 记录这轮减少了多少失败 case。

5. **再跑聚合回归**
   - 用 [scripts/run_auto_test_all_domains.py](../../scripts/run_auto_test_all_domains.py) 执行全量聚合；
   - 当前聚合模式是“**每个 domain 独立初始化并单独执行**”，用于避免跨 domain 状态污染。

6. **输出轮次结论**
   - 只保留：
     - 总 case 数；
     - 通过 / 失败数；
     - 新增失败；
     - 已修复失败；
     - 遗留失败；
     - 每个遗留失败的归因分类。

### 14.2 每轮只保留 4 份信息

为了避免上下文失控，每轮总结时只保留下面 4 份信息：

1. **总计快照**
   - `total_domains`
   - `passed_domains`
   - `failed_domains`
   - `total_cases`
   - `passed_cases`
   - `failed_cases`

2. **变化清单**
   - 本轮修复了哪些 case；
   - 本轮新增了哪些 case；
   - 哪些 case 没变化。

3. **失败分类表**
   - `script_design`：脚本设计 / 运行配置 / 断言策略问题；
   - `push_system`：状态机、绑定、依赖、持久化、推送链路问题；
   - `backend`：后端接口、后端读写模型、后端返回值问题；
   - `data`：测试数据不满足前置条件或数据本身不合法；
   - `flaky`：暂时只在聚合中出现、单跑不能稳定复现的问题。

4. **下一轮唯一目标**
   - 下一轮最多列 1~3 个目标；
   - 不把所有猜测都带到下一轮。

### 14.3 轮次汇总格式

每轮报告建议固定成下面的结构：

1. **Round N 总计**
2. **与 Round N-1 对比**
3. **本轮修复**
4. **本轮新增**
5. **遗留问题分类**
6. **下一轮目标**

建议直接从聚合报告的 `summary.json` 中抽取总计，避免手工整理时带入过多细节。

### 14.4 建议的对比口径

比较两轮结果时，统一只按 `domain::round_name` 比较，不按日志文本比较。

也就是把每个 case 视为一个稳定键，例如：

- `project::round_2_base_info_update`
- `project::round_3_investment_update`
- `material_detail::round_2_update`

这样做的好处是：

- 总数稳定；
- 对比简单；
- 很容易看出某个 case 是“由失败转通过”，还是“仍然失败”。

### 14.5 建议的推进策略

对于 10+ domain、数十个 case 的回归，建议始终按下面方式推进：

1. **先消灭稳定复现的单点失败**
   - 先修能稳定复现的问题，不先追偶发问题。

2. **再处理共享根因**
   - 如果多个 domain 共用同一个 helper、同一个运行配置、同一种绑定策略，优先处理这一层。

3. **最后单列 flaky**
   - 聚合里偶发失败、单跑通过的问题，不直接混入主结论；
   - 先标成 `flaky`，待重复出现后再升级处理。

### 14.6 当前这套系统中的实际判定方法

当前推荐把问题按下面顺序判定：

1. **先看单 domain 是否稳定失败**
   - 如果单跑失败，优先查该 domain 自身脚本、数据、后端接口。

2. **再看聚合回归是否额外引入失败**
   - 如果单跑通过、聚合失败，优先怀疑：
     - 运行顺序；
     - 环境残留；
     - 报告聚合方式；
     - 偶发后端错误。

3. **最后看后端是否写了但读不出来**
   - 如果 update 接口已命中，但回读字段缺失或不一致，要明确归类为 `backend`，而不是继续修改测试脚本绕过。

### 14.7 本文档要求的轮次目标

每一轮结束后，必须能一句话回答下面 3 个问题：

1. **总计有没有变好？**
2. **变好是因为修了脚本、推送系统，还是确认了后端问题？**
3. **下一轮只追哪一个主问题？**

如果一轮总结不能回答这 3 个问题，就说明这轮上下文保留得太多、结论还不够收敛。
