From 4730cd527db11c1d8d80336a9c2d94c9df95458f Mon Sep 17 00:00:00 2001 From: chendelian <116870791@qq.com> Date: Sun, 3 May 2026 18:08:57 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E5=AE=8C=E5=96=84=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/README.md | 52 +++++++++++++++++ tests/desktop/README.md | 2 + tests/fixtures/README.md | 10 ++++ tests/integration/README.md | 10 ++++ tests/samples/README.md | 113 +++++++++++++++++++++++++++++++++++- 5 files changed, 184 insertions(+), 3 deletions(-) diff --git a/tests/README.md b/tests/README.md index c95dfe6..4e881c7 100644 --- a/tests/README.md +++ b/tests/README.md @@ -6,6 +6,43 @@ --- +## 我该把测试写在哪里? + +人类开发者与 Cursor 等 AI 工具在加用例前,先对照下表决定**文件落点**与**是否默认执行**: + +| 场景 | 放在哪里 | 默认是否运行 | 说明 | +|------|----------|--------------|------| +| CLI / health / version / metadata / runtime 路径 | `tests/test_*.py` | 是 | 默认必跑,保持轻量、无外联 | +| 纯函数 / 业务规则 | `tests/test_*.py` | 是 | 不依赖真实外部系统 | +| service 层契约 | 从 `tests/samples/test_service_contract.py.sample` 复制到 `tests/test_service_contract.py` | 是,复制后 | 用 `FakeAdapter` / stub 注入外部依赖 | +| golden fixture 回归 | 从 `tests/samples/test_golden_cases.py.sample` 复制到 `tests/test_golden_cases.py` | 是,复制后 | 输入样例 + 期望输出,适合解析、计算、校验类技能 | +| 仿真 API | `tests/integration/test_simulator_api_contract.py.sample` | 否 | 复制并设置 `OPENCLAW_TEST_TARGET=simulator_api`(或兼容键名 `OPENCLOW_TEST_TARGET`)后按需运行 | +| 仿真 RPA / 页面操作 | `tests/integration/test_simulator_rpa_contract.py.sample` | 否 | 用 localhost / 仿真页面,不碰真实系统 | +| 真实 API | `tests/integration/test_real_api_contract.py.sample` | 否 | 必须 `OPENCLAW_TEST_TARGET=real_api` 且 `ALLOW_REAL_API=1` | +| 真实 RPA | `tests/integration/test_real_rpa_contract.py.sample` | 否 | 最高风险,只能手动触发 | +| 桌面宿主 E2E | `tests/desktop/test_desktop_smoke.py.sample` | 否 | 需要 pytest + `jiangchang_desktop_sdk` | + +说明:`python tests/run_tests.py` **只**收集 `tests/` **根目录**下的 `test_*.py`;子目录里的 `*.sample` 与 desktop 用例**不会**被默认发现。 + +--- + +## 新技能最小测试清单 + +从本模板复制出新技能仓库后,建议至少逐项确认: + +- [ ] `python tests/run_tests.py -v` 能通过。 +- [ ] `python scripts/main.py health` 能通过。 +- [ ] `python scripts/main.py version` 输出 JSON,且 `skill` 与目录名 / `SKILL.md` / `constants.SKILL_SLUG` 一致。 +- [ ] 所有 DB / 文件写入都在 `IsolatedDataRoot`(或等价隔离)下测试,不写真实数据目录。 +- [ ] 外部系统默认使用 mock / `FakeAdapter`,不访问真实 API,不打开真实 RPA。 +- [ ] 至少有 1 个成功路径测试。 +- [ ] 至少有 1 个缺必填字段 / 非法输入测试。 +- [ ] 如果有 adapter,至少覆盖 timeout / unauthorized / invalid response(可用 fake 模拟)。 +- [ ] 如果有解析 / 计算 / 校验类业务,至少保留 1 组 golden fixture(脱敏)。 +- [ ] 真实 API / 真实 RPA 测试只放 `tests/integration/`,并且默认 `.sample`,不默认运行。 + +--- + ## 1. 默认必跑(unittest) 在**技能仓库根目录**执行: @@ -113,6 +150,21 @@ python tests/run_tests.py test_cli_smoke --- +## AI 编程工具注意事项(Cursor 等) + +以下约束适用于为本仓库或复制出的技能编写/修改测试时,**避免误伤业务与生产**: + +- **不要为了**让测试通过去改业务代码,除非用户明确要求修改实现。 +- **不要**把真实外部系统调用写进默认的 `tests/test_*.py`(默认套件须无外联、无真实浏览器)。 +- **不要**把 `*.sample` 改名为可执行测试或移入根目录并去掉 `.sample`,除非用户明确要求启用 integration / 真实联调。 +- **不要**硬编码真实凭证、token、cookie、生产 URL;占位请用明显虚构域名(如 `example.invalid`)或文档约定的 `credential_ref`。 +- 默认测试只能使用 **mock、stub、`FakeAdapter`、fixtures、隔离 SQLite** 等安全手段。 +- 业务逻辑应优先在 **service 层** 编写与测试,不要只断言 CLI 文案。 +- 异常路径应返回**结构化错误**(如统一错误码/字段),避免裸异常直接穿透到 CLI。 +- 有真实系统联调需求时,测试应写在 `tests/integration/`,并配合 `OPENCLAW_TEST_TARGET` / `ALLOW_REAL_*` / `ALLOW_WRITE_ACTIONS` 等**显式开关**;不得默认开启。 + +--- + ## 6. 与 content-manager 的关系 - **未**复制 article / media / prompt / NotebookLM 等业务测试。 diff --git a/tests/desktop/README.md b/tests/desktop/README.md index 338187d..0e11662 100644 --- a/tests/desktop/README.md +++ b/tests/desktop/README.md @@ -30,3 +30,5 @@ pytest tests/desktop/test_desktop_smoke.py -v -s - **后端 / CLI / DB**:请使用仓库根目录下 `python tests/run_tests.py`(见 `tests/README.md`)。 - **桌面 E2E**:仅在本目录用 **pytest** 维护,二者互补、互不替代。 + +若仅需验证 **service 业务逻辑**、契约与 golden 回归,**不要**写 desktop E2E;desktop 只用于验证**宿主 UI、路由、`jiangchang://` 协议、真实用户交互链路**等与桌面宿主强相关的行为。 diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md index c78193e..34b29ac 100644 --- a/tests/fixtures/README.md +++ b/tests/fixtures/README.md @@ -15,3 +15,13 @@ | `sample_request.json` | 通用请求体样例,供 ``tests/samples/test_golden_cases.py.sample`` 演示读取。 | | `expected_response.json` | 与上配套的期望摘要字段(业务复制后按域替换)。 | | `adapter_profiles.sample.yaml` | 五类运行方式(mock / simulator_api / simulator_rpa / real_api / real_rpa)的配置表达样例。 | + +--- + +## 给 AI 工具的替换规则 + +- 可以按业务**重命名字段**、增减键名,但必须保持**虚构、脱敏**;不得把真实客户标识原样搬进仓库。 +- 可以**新增**更多 fixture 文件,例如 `case_valid_001.json`、`case_missing_required_001.json`,并在测试中通过相对路径读取。 +- **禁止**将真实企业数据、真实单证截图中的文字、真实报关要素未经处理直接复制进 `fixtures/`。 +- 若用例来自真实客户样本,须先**脱敏、泛化、替换编号与地名**,再作为模板数据提交。 + diff --git a/tests/integration/README.md b/tests/integration/README.md index 359693f..643f7f0 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -5,6 +5,16 @@ - ``python tests/run_tests.py`` 只收集 ``tests/`` 根目录下 ``test_*.py``,**不会**进入本目录。 - 本目录下文件一律为 ``*.py.sample``,pytest / unittest **默认也不会**按普通 ``test_*.py`` 收集。 +## 与 samples 的区别 + +| 维度 | `tests/samples/` | `tests/integration/` | +|------|------------------|------------------------| +| 目的 | 默认可**升级**为根目录 `test_*.py` 的 **业务单元 / service 契约 / golden** 范式 | **外联联调**范式:网络、浏览器、仿真或真实系统 | +| 默认执行 | `.sample` 不执行;复制到根目录并改名后才进入默认套件 | **永远不**作为默认 unittest 的一部分;复制后仍需显式环境变量与手动/CI 任务触发 | +| 典型内容 | `FakeAdapter`、fixture、隔离 DB | `localhost` 仿真、真实 API(授权)、真实 RPA(最高风险) | + +**原则**:只要会访问**网络**、**浏览器**、**真实或仿真外部系统**,应优先放在 `integration`(并保持 `.sample` 直至团队同意启用),而不是写进默认 `tests/test_*.py`。 + ## 何时使用 | 场景 | 建议 ``OPENCLAW_TEST_TARGET`` | 其它条件 | diff --git a/tests/samples/README.md b/tests/samples/README.md index 225da81..952b279 100644 --- a/tests/samples/README.md +++ b/tests/samples/README.md @@ -1,5 +1,112 @@ -# samples(样例,默认不执行) +# samples(业务测试样例,默认不执行) -本目录下的 ``*.py.sample`` **不会被** ``python tests/run_tests.py`` 收集。 +本目录存放**可复制到根目录的业务测试范式**,面向「企业数字员工技能」中 **ERP、TMS、WMS、船司、报关、邮件、LLM、RPA** 等外联场景下的 **service / golden** 测法说明。 -复制为 ``tests/test_*.py``(去掉 ``.sample``)后,再按业务实现 service / golden 断言。 +--- + +## 作用 + +- 这里的文件是 **`.py.sample`**:`python tests/run_tests.py` **不会**收集本目录(`run_tests.py` 只发现 `tests/` 根目录下的 `test_*.py`)。 +- 人类或 AI 需要启用范式时:**复制**到 `tests/test_<名称>.py`(去掉 `.sample`),再按业务改 import、断言与 fixture 路径。 +- **禁止**把真实联调、真实网络、真实 RPA 写进复制后的默认用例;此类内容应放在 `tests/integration/*.sample` 并配合环境变量开关。 + +--- + +## 两类样例文件 + +### 1. `test_service_contract.py.sample` + +**适合:** + +- 技能有清晰的 **service 层入口**,例如 `run(...)`、`handle(...)`、`execute(...)`。 +- 需要验证:**成功路径**、**参数/校验错误**、**adapter 异常被转换为业务可读错误**。 +- 需要用 **`FakeAdapter`**(见 `tests/adapter_test_utils.py`)或自建 **stub** 替代 ERP、TMS、WMS、船司、报关、LLM、RPA 等,**不**发真实 HTTP、**不**开真实浏览器。 + +### 2. `test_golden_cases.py.sample` + +**适合:** + +- **解析类**:邮件/单证/PDF/Excel 等结构化输出。 +- **计算类**:费用、税费、分摊、账单字段。 +- **校验类**:合规、字段一致性、状态机规则。 + +推荐配合根目录下的 **`tests/fixtures/sample_request.json`** 与 **`tests/fixtures/expected_response.json`**(复制技能后按域替换字段,保持脱敏)。 + +--- + +## 如何启用 service contract 样例 + +1. **复制文件** + `tests/samples/test_service_contract.py.sample` + → `tests/test_service_contract.py`(或 `tests/test__service_contract.py`,须符合根目录 `test_*.py` 命名以便 `run_tests.py` 收集)。 + +2. **替换示例 import** + 把注释中的示意: + `# from service.your_service import run` + 改为真实模块,例如: + `from service.quote_service import run_quote` + (具体路径以技能代码为准。) + +3. **注入假外部依赖** + - **不要**在默认用例里直接 `requests` 访问 ERP/TMS/WMS 等。 + - **不要**打开真实网页或驱动真实 RPA。 + - **不要**调用真实 LLM;可 stub 返回固定 JSON。 + - 对 **timeout / unauthorized / invalid_response** 等分支,用 `FakeAdapter` 或自建 fake 模拟(与 `adapter_test_utils` 思路一致)。 + +4. **至少保留三类断言习惯** + - **成功路径**:返回 `success: true` 或与技能约定的成功结构。 + - **参数错误**:缺必填字段时返回**结构化** error(字段/码),而非未捕获裸异常。 + - **外部异常**:adapter 超时、鉴权失败、脏响应等被转换为**可读、可重试标识明确**的错误形态(按产品约定)。 + +5. **给 AI 的提示** + 复制后全文搜索 `your_service`、`TODO`、`伪代码` 等占位符并替换;不要留下指向真实客户的示例数据。 + +--- + +## 如何启用 golden case 样例 + +1. **复制文件** + `tests/samples/test_golden_cases.py.sample` + → `tests/test_golden_cases.py`(或 `tests/test__golden.py`)。 + +2. **按业务修改 fixtures**(仍须脱敏) + - `tests/fixtures/sample_request.json`:请求或输入快照。 + - `tests/fixtures/expected_response.json`:期望摘要或关键字段。 + 可新增多组文件,如 `case_valid_001.json`,并在测试中显式读取路径。 + +3. **断言建议(避免脆弱用例)** + - 比较**关键字段**,避免对整段 JSON 做机械全文 diff。 + - **忽略**时间戳、随机 id、`trace_id` 等非确定字段。 + - 对列表:断言**长度**、**关键元素**、**排序规则**(若业务保证顺序)。 + - 对金额/数量:断言 Decimal 字符串、或允许文档约定的小范围误差。 + +4. **与 service 的关系** + golden 用例仍应通过 **service 层**(或纯函数)得到输出,再与 fixture 对比,而不是从 CLI 字符串 scrape。 + +--- + +## 不应该写在 samples 复制件里的内容 + +以下若出现,应移到 **`tests/integration/*.sample`** 或 **`tests/desktop/*.sample`**,并加环境变量授权说明: + +- 访问**真实** API 或内网服务。 +- 打开**真实** RPA 页面或生产门户。 +- 读取**真实**客户文件、生产数据库。 +- 在测试中写入**真实**数据根或共享盘。 +- 任何真实 **token / cookie / 密码** 字面量。 +- 耗时极长、强依赖宿主 UI 抖动、无法在 CI 稳定复现的用例(不要塞进默认 `tests/test_*.py`)。 + +--- + +## 推荐命名(复制到根目录后) + +| 用途 | 示例文件名 | +|------|------------| +| Service 契约 | `tests/test_service_contract.py` | +| Golden / fixture 回归 | `tests/test_golden_cases.py` | +| 领域规则 | `tests/test__rules.py` | +| 校验逻辑 | `tests/test__validation.py` | + +命名需满足:`tests/` 根目录下 `test_*.py`,以便 `python tests/run_tests.py` 默认发现。 + +更多分层说明见 **`tests/README.md`**(「我该把测试写在哪里?」与「新技能最小测试清单」)。