10 KiB
测试开发指南
面向复制 skill-template 后的新业务 skill:如何把自动化测试当作一等公民,而不是等业务写完再补文档级别的空话。本文串起模板自带的 unittest 入口、tests/ 目录分层与安全档位约定;更细的开关取值、表格字段与环境变量组合仍以 tests/README.md 为权威来源。建议你随手开一个编辑器分页:references/TESTING.md(本篇)、references/DEVELOPMENT.md(整体节奏)、tests/README.md(落地细则)。
默认心智模型可以用一句话概括:根目录 test_*.py = CI / 本地每次提交都应能通过的无外联套件;*integration*、*.sample、desktop/ = 只在人被明确要求时才启用的高风险或重量级路径。
1. 测试体系总览
本模板把测试分成四层漏斗:默认必跑(unittest + run_tests.py)、从 tests/samples/ 按需复制的 service / golden、放在 tests/integration/ 默认不落盘的仿真或真实联调范式(多数仍是 .sample)、以及 desktop E2E(pytest + 宿主 SDK)。
tests/run_tests.py 只做三件事:把 scripts/ 与 tests/ 放进 sys.path、做 Windows UTF-8 包装、收集 tests/ 根目录下的 test_*.py。它不递归子目录——这正是刻意的安全边界:不想让 AI 或拷贝粘贴 accidentally 把 integration 拉进默认套件。
当你在设计一个新 skill 的测试策略时,请先问自己:这段代码在没有外部凭证与浏览器的前提下是否有意义? 若有,留在默认套件;若无,放进 integration / .sample,并要求明确的 OPENCLAW_TEST_TARGET 组合开关。
更深表格化的目录映射、FakeAdapter 与 profile 的耦合细节见 tests/README.md 开头章节『我该把测试写在哪里』。
2. 默认必跑测试要做什么
必跑套件要像一个紧张的守门员:快、确定、离线。典型覆盖:
- CLI:导入
cli.app走解析链路、health(runtime diagnostics)/version/logs/log-get冒烟; - 架构守护:无
scripts/jiangchang_skill_core/、platform-kit>=1.0.11导入来源、文档/runtime 标准(见test_platform_import.py等); - 真实 subprocess:
tests/test_entrypoint_subprocess.py再调用一遍python scripts/main.py,防路径漂移; - 运行时:
runtime_paths与 **CLAW_*/JIANGCHANG_*并发兜底; SKILL.mdYAML slug vsconstants.SKILL_SLUG;- SQLite 骨架:
task_logs创建幂等与仓储读写; - adapter profile:
tests/test_adapter_profile_policy.py+adapter_test_utils——验证在未授权情况下绝不误判开启真实网络/RPA。
原则:tests/test_*.py 不允许隐形访问外网、不允许拉起真实浏览器、不允许读写开发者机器的真实数据根。如果需要仿真服务器,也应仅在 integration(并由档位变量放行)。
套件能力与表格参见 tests/README.md 「1.2 默认套件覆盖」。
3. 数据隔离:IsolatedDataRoot
tests/_support.py 提供的上下文管理器 IsolatedDataRoot():进入一个专用临时目录,镜像写入四套变量:CLAW_DATA_ROOT、JIANGCHANG_DATA_ROOT(同一 tempfile)、以及用户镜像 ID:CLAW_USER_ID、JIANGCHANG_USER_ID → _test。
结束时:恢复原 environ,删除目录。
这样可以断言:SQLite DB / spill files / caches 都在 sandbox;不会在开发者桌面遗留 {REAL_ROOT}。
示例:
from _support import IsolatedDataRoot
def test_whatever():
with IsolatedDataRoot():
from db.connection import init_db
init_db()
# …断言读写均在隔离路径…
不要把真实凭证路径硬编码进默认测试:隔离不等于你有权触碰真实目录。
4. 测试目标档位(OPENCLAW_TEST_TARGET)
模板采用统一闸门:你想跑到哪一层外部世界,就用变量明说。合法档位(非法值会让 helper 抛错)如下——直接摘录自 tests/README.md §5.1:
| 取值 | 含义 |
|---|---|
unit |
默认:单元 / 内存 / mock,不跑仿真与真实外联 |
mock |
与 unit 类似的安全档位(显式语义) |
simulator_api |
仅允许 仿真 HTTP 类集成(如 localhost) |
simulator_rpa |
仅允许 仿真页面 / 录播 RPA |
real_api |
真实 API(另需 ALLOW_REAL_API=1) |
real_rpa |
真实 RPA(另需 ALLOW_REAL_RPA=1) |
未设置环境变量 ⇒ 等价 unit。
授权开关(显式 1)语义 ALLOW_REAL_API / ALLOW_REAL_RPA / ALLOW_WRITE_ACTIONS——摘录 tests/README.md §5.2:
| 变量 | 作用 |
|---|---|
ALLOW_REAL_API=1 |
允许 real_api profile 访问真实 HTTP 通道 |
ALLOW_REAL_RPA=1 |
允许 real_rpa profile 驱动真实浏览器/RPA |
ALLOW_WRITE_ACTIONS=1 |
在 real_* 下允许写操作(提交表单、下单等) |
默认策略摘要见 §5.3:不要在 unittest 必跑路径误把闸门打开。
兼容别名:OPENCLOW_TEST_TARGET(历史拼写)。
5. FakeAdapter:怎么模拟外部系统
tests/adapter_test_utils.py 暴露 FakeAdapter,典型四种 mode:
| mode | 用途 |
|---|---|
success |
构造干净的成功响应路径 |
timeout |
模拟悬挂 / 慢链路 |
invalid_response |
畸形负载 / schema drift |
unauthorized |
token / license / cookie 失效语义 |
何时用它:service 层出现『调用第三方 HTTP / RPA stub』但又不能把真实系统纳入 CI。契约测试(复制 tests/samples/test_service_contract.py.sample)应优先组合 FakeAdapter,而不是直接把 CLI when-json 断言堆上天。
把它看成:你把不确定性收敛到可控的测试矩阵里,而不是在生产日志里才第一次看到错位字段。
6. 怎么从 .sample 启用一个测试
步骤模板:
- 找到范式文件(例如
tests/samples/test_service_contract.py.sample)。 - 复制到
tests/根:tests/test_service_contract.py(去掉.sample)。 - 打开副本:替换占位函数名 /技能特有枚举 / adapter profile,删掉与本技能无关的示例断言。
- 本地执行:
python tests/run_tests.py -v [可选筛选关键词]。 - 不要把 integration
.sample批量改名混进根目录——除非你已经读过tests/integration/README.md的风险清单。
Golden fixture 流程同理(tests/samples/test_golden_cases.py.sample)。
7. 真实联调测试的安全约束
任何 touching 真实租户数据 的路径:
- 禁止硬编码 token / cookie / 内部域名落入仓库;
- 禁止默认套件隐式导入 integration;
- 真实 RPA 只能标记为手动触发(双人复核 / 本地
.env不入库)。
范式阅读 tests/integration/README.md:那里有针对凭证来源、目录 artifact 忽略策略的补充。
记住:测试代码也是一种部署面,别把 staging 凭证写死进仓库。
8. 新 skill 的最小测试清单
以下清单 原文摘自 tests/README.md 「新技能最小测试清单」(复制新仓库后逐项勾选):
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,不默认运行。
9. AI 编程工具使用测试时的红线
改编自 tests/README.md 「AI 编程工具注意事项」,压缩成 skill 开发者视角:
| 红线 | 解释 |
|---|---|
| 不改业务凑测试 | 除非需求变更已确认,否则不要为了让 CI 变绿而砍掉业务分支 |
| 默认套件零外联 | 不把真实 HTTP / 浏览器写进 tests/test_*.py |
.sample 尊重 |
集成范式改名前先读完 README;别让 .sample 悄悄变成根测试 |
| 零硬编码凭证 | token / cookie / 生产 URL → 用虚构域名或 vault ref |
| mock 优先 | 逻辑应在 service + FakeAdapter,而不是巨胖 CLI 断言 |
| 结构化错误 | 断言错误码字段,而不是 substring of stderr 漂移集合 |
| integration = 显式开关 | OPENCLAW_TEST_TARGET + ALLOW_REAL_* / ALLOW_WRITE_ACTIONS |
10. 测试和发布的关系
在运行 release.ps1 之前,python tests/run_tests.py -v 必须绿色。失败的默认套件意味着:
- 打包路径可能根本不可运行;
- CI 加密前的静态假设可能在宿主崩溃;
- metadata slug 漂移将被市场拒绝。
把『本地 unittest 绿』视作 tag 的前置条件,而不是『有空再跑』。发布流水线成功后仍要做安装验证——那是另一个维度;测试是第一个维度。
延伸阅读
tests/README.md— 表格、变量与目录细则references/DEVELOPMENT.md§14.5 — 测试驱动的开发顺序tests/integration/README.md— 高风险用法