方向反转:此前 SKILL.md 是「模板渲染产物」、sap-cli 是源;现 sap-cli 归档, sap-cli-skill 承接开发与分发,SKILL.md 回归手工维护的正本。 迁移(来自 sap-cli,共 104 文件): - tests/ 692 例测试(15 个文件的内联 sys.path 改指 assets/) - openspec/ SDD 规格与归档变更(42 文件) - docs/ 开发文档与 ADT 原理(含 dev/CLAUDE.md、AGENTS.md) - .claude/ rules 副本 + settings.json(供 Claude Code) - .github/ .hermes/ .pre-commit-config.yaml .editorconfig CLAUDE.md - scripts/ 保持仅 setup.py(pack_skill.py 已随旧仓归档,不迁) 修复(迁移暴露的真实缺陷): - assets/pyproject.toml 的 build-backend 写作 `setuptools.backends._legacy:_Backend`, 该模块在 setuptools 中不存在 → `pip install -e` 从来装不上。改为 build_meta。 实测:临时 venv 安装成功,sap-cli --help 正常列出 31 个命令 - pyproject readme 指向不存在的 assets/README.md(editable 安装会失败)→ 改内联文本 - pyproject urls 改指 sap-cli-skill 机制调整: - .github/workflows/ci.yml 适配 assets/ 布局;顶部注明该工作流仅 GitHub 执行, 本仓在 Gitee 不会自动跑 - pre-commit 增本地测试门禁(Gitee 上真正生效的那道) - .gitignore 合并旧仓完整规则(保留 log/ 下 md 知识库入库,只忽略运行日志) - 大文件上限 100KB→1MB(架构图 512KB) 守卫测试 tests/unit/test_repo_guards.py(10 → 18 例): - SKILL.md 须记录 parser 全部 CLI 命令 / 铁律 1-5 须为真实小节标题 / 示例不得违反铁律 5 - references/ 规则齐备;.claude/rules 与 references 必须一致(实测抓到一次真实漂移) - VERSION == sapcli.__version__ == README 版本 - 仓内不得再出现 pack_skill.py / skill-src(防废弃流程回潮) 698 tests OK;editable 安装与 CLI 入口经临时 venv 实测通过。 docs/RELEASING.md 重写为单源开发流程。
13 KiB
13 KiB
ADT 工具集测试报告
测试目标:验证
main.py的 create / info / download / sync / delete 五大功能在各种场景下的正确性。测试日期:2026-05-20 | 测试脚本:test_main.py
前置条件
- SAP 系统可连接(配置文件
config.ini正确) - Python 3.10+ 和
requests库已安装 - 系统中存在至少一个 Modifiable 状态的传输请求
- 系统中存在以下已有对象用于查询测试:
- class:
ZCL_SAP_MCP_HANDLER - report:
ZIDTR_IMPORT_BOM - domain:
ZSAPILOT_OBJECT_TYPE - dataelement:
ZSAPILOT_OBJECT_NAME - table:
ZSAPILOT_OBJ
- class:
测试覆盖矩阵
操作(行) × 对象类型(列) 的交叉覆盖情况。 ✅ = 已测试通过 | ⬜ = 不适用 | ➖ = 未覆盖(系统限制)
| 操作 \ 对象类型 | report | class | function | functiongroup | interface | domain | dataelement | table | tabletype |
|---|---|---|---|---|---|---|---|---|---|
| create | ✅ SC1 | ✅ SC2 | ✅ SC3 | ✅ SC3 | ✅ SC4 | 🔒 | 🔒 | 🔒 | ⬜ |
| info | ✅ SC1 | ✅ SC2 | ✅ SC3 | ✅ | ✅ SC4 | ✅ SC9 | ✅ SC9 | ✅ SC9 | ✅ SC9 |
| download | ✅ SC1 | ✅ SC2 | ✅ SC3 | ✅ SC13 | ✅ SC4 | 🔒 | 🔒 | 🔒 | ⬜ |
| sync | ✅ SC1,SC5 | ✅ SC2 | ✅ SC3 | ✅ SC13 | ✅ SC4 | 🔒 | 🔒 | 🔒 | ⬜ |
| delete | ✅ SC1 | ✅ SC2 | ✅ SC3 | ✅ SC3 | ✅ SC4 | 🔒 | 🔒 | 🔒 | ⬜ |
图例:
- ✅ 已测试通过
- ⬜ 不适用(VIT 端点不支持源码读写和锁定操作)
- 🔒 SAP 系统限制(当前目标系统 NW 7.40 的 ADT API 不支持 DDIC 对象的源码读写和创建/删除)
DDIC 对象(🔒)说明:
info可用(使用Accept: */*头),已在 SC9 验证create/download/sync/delete不可用 — 经实际探测,SAP NetWeaver 7.40 的 ADT 框架对 DDIC 对象(domain/dataelement/table)不提供源码读写端点(返回 404/406)- 在 SAP S/4HANA 或更高版本 NetWeaver 系统上,这些操作可能可用
- 代码层面已实现完整支持(URI 映射、创建模板、argparse choices 均已配置),仅受目标系统 API 限制
tabletype(⬜)说明:
info已可用 — 通过 VIT 端点(/sap/bc/adt/vit/wb/object_type/ttypda/object_name/{name})查询元数据,已在 SC9 验证download/sync/delete不可用 — VIT 端点为只读元数据接口,不提供源码读写和锁定操作create不可用 — 无可用的集合端点用于创建新对象
场景测试详情
场景组 happy — 正向完整流程
核心流程:创建 → 查询 → 下载 → 修改 → 同步激活 → 验证修改 → 删除 → 验证不存在
场景 1: 正向完整流程 — report
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|---|---|---|---|---|
| SC1-1 | 创建 report | create --name ZTEST_SC1_RPT_xxx --type report |
rc == 0 | ✅ |
| SC1-2 | 查询验证 | info --name ZTEST_SC1_RPT_xxx --type report |
rc == 0 且名称匹配 | ✅ |
| SC1-3 | 下载源码 | download --name ZTEST_SC1_RPT_xxx --type report |
rc == 0 且文件存在 | ✅ |
| SC1-4 | 修改后同步激活 | 修改源码 → sync |
语法检查通过 + 激活成功 | ✅ |
| SC1-5 | 验证修改持久化 | 再次 download |
源码包含修改标记 | ✅ |
| SC1-6 | 删除对象 | delete --name ZTEST_SC1_RPT_xxx |
删除成功 | ✅ |
| SC1-7 | 验证已不存在 | info |
rc != 0 | ✅ |
场景 2: 正向完整流程 — class
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|---|---|---|---|---|
| SC2-1 | 创建 class | create --name ZTEST_SC2_CLS_xxx --type class |
rc == 0 | ✅ |
| SC2-2 | 查询验证 | info --name ZTEST_SC2_CLS_xxx --type class |
rc == 0 且名称匹配 | ✅ |
| SC2-3 | 下载源码 | download --name ZTEST_SC2_CLS_xxx --type class |
rc == 0 且文件存在 | ✅ |
| SC2-4 | 修改后同步激活 | 修改源码 → sync |
语法检查通过 + 激活成功 | ✅ |
| SC2-5 | 验证修改持久化 | 再次 download |
源码包含修改标记 | ✅ |
| SC2-6 | 删除对象 | delete --name ZTEST_SC2_CLS_xxx |
删除成功 | ✅ |
| SC2-7 | 验证已不存在 | info |
rc != 0 | ✅ |
场景 3: 正向完整流程 — function + functiongroup
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|---|---|---|---|---|
| SC3-1 | 创建函数组 | create --name ZTEST_SC3_FG_xxx --type functiongroup |
rc == 0 | ✅ |
| SC3-2 | info 验证函数组存在 | info --name ZTEST_SC3_FG_xxx --type functiongroup |
rc == 0 且名称匹配 | ✅ |
| SC3-3 | 创建函数 | create --name ZTEST_SC3_FG_xxx/ZTEST_SC3_FM_xxx --type function |
rc == 0 | ✅ |
| SC3-4 | info 验证函数存在 | info |
rc == 0 且名称匹配 | ✅ |
| SC3-5 | 下载函数源码 | download |
rc == 0 且文件存在 | ✅ |
| SC3-6 | 首次修改 → 同步激活 | 修改源码 → sync |
语法检查通过 + 激活成功 | ✅ |
| SC3-7 | 再次下载验证修改持久化 | download |
rc == 0 且文件包含修改标记 | ✅ |
| SC3-7b | 验证修改内容已持久化 | 读取下载文件 | 包含 "SC3 TEST MARKER" | ✅ |
| SC3-8 | 二次修改 → 同步激活 | 替换标记 → sync |
语法检查通过 + 激活成功 | ✅ |
| SC3-9 | info 验证函数仍存在 | info --name ... --type function |
rc == 0 且名称匹配 | ✅ |
| SC3-10 | info 验证函数组仍存在 | info --name ... --type functiongroup |
rc == 0 且名称匹配 | ✅ |
| SC3-11 | 删除函数 | delete 函数模块 |
删除成功 | ✅ |
| SC3-12 | info 验证函数组在函数删除后仍存在 | info --name ... --type functiongroup |
rc == 0(函数组独立于函数) | ✅ |
| SC3-13 | 删除函数组 | delete 函数组 |
删除成功 | ✅ |
| SC3-14 | info 验证函数组已不存在 | info |
rc != 0 | ✅ |
场景 4: 正向完整流程 — interface
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|---|---|---|---|---|
| SC4-1 | 创建 interface | create --name ZTEST_SC4_INT_xxx --type interface |
rc == 0 | ✅ |
| SC4-2 | 查询验证 | info --name ZTEST_SC4_INT_xxx --type interface |
rc == 0 且名称匹配 | ✅ |
| SC4-3 | 下载源码 | download --name ZTEST_SC4_INT_xxx --type interface |
rc == 0 且文件存在 | ✅ |
| SC4-4 | 修改后同步激活 | 修改源码 → sync |
语法检查通过 + 激活成功 | ✅ |
| SC4-5 | 验证修改持久化 | 再次 download |
源码包含修改标记 | ✅ |
| SC4-6 | 删除对象 | delete --name ZTEST_SC4_INT_xxx |
删除成功 | ✅ |
| SC4-7 | 验证已不存在 | info |
rc != 0 | ✅ |
场景组 error — 逆向错误处理
场景 5: 逆向 — 语法错误
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|---|---|---|---|---|
| SC5-1 | 创建 report | create |
rc == 0 | ✅ |
| SC5-2 | 下载源码 | download |
rc == 0 且文件存在 | ✅ |
| SC5-3 | 注入无效语法 → 同步 | 添加 INVALID_SYNTAX_HERE. → sync |
rc != 0 且包含错误信息 | ✅ |
| SC5-4 | 恢复源码 → 重新同步 | 还原源码 → sync |
rc == 0 + 激活成功 | ✅ |
| SC5-5 | 清理删除 | delete |
删除成功 | ✅ |
场景 6: 逆向 — 对象不存在
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|---|---|---|---|---|
| SC6-1 | 查询不存在的 class | info --name Z_NOT_EXIST_99999 --type class |
rc != 0 且包含"不存在" | ✅ |
| SC6-2 | 下载不存在的 report | download --name Z_NOT_EXIST_99999 |
rc != 0 | ✅ |
| SC6-3 | 删除不存在的 report | delete --name Z_NOT_EXIST_99999 |
rc != 0 | ✅ |
场景 7: 逆向 — 重复创建
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|---|---|---|---|---|
| SC7-1 | 首次创建 | create --name ZTEST_SC7_RPT_xxx |
rc == 0 | ✅ |
| SC7-2 | 重复创建 → 报错 | 再次 create 同名对象 |
rc != 0 | ✅ |
| SC7-3 | 清理删除 | delete |
删除成功 | ✅ |
场景组 edge — 边界场景
场景 8: 边界 — 同步自动创建
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|---|---|---|---|---|
| SC8-1 | 准备本地源码 | 创建 .abap 文件 | 文件存在 | ✅ |
| SC8-2 | sync 自动创建 | sync 不存在的对象 |
rc == 0(自动创建) | ✅ |
| SC8-3 | info 验证存在 | info |
rc == 0 且名称匹配 | ✅ |
| SC8-4 | 清理删除 | delete |
删除成功 | ✅ |
场景 9: 边界 — DDIC 对象查询
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|---|---|---|---|---|
| SC9-1 | info domain | info --name ZSAPILOT_OBJECT_TYPE --type domain |
rc == 0 且名称匹配 | ✅ |
| SC9-2 | info dataelement | info --name ZSAPILOT_OBJECT_NAME --type dataelement |
rc == 0 | ✅ |
| SC9-3 | info table | info --name ZSAPILOT_OBJ --type table |
rc == 0 | ✅ |
| SC9-4 | info tabletype | info --name ZSAPILOT_OBJECT_TT --type tabletype |
rc == 0 且名称匹配 | ✅ |
场景 10: 边界 — 删除安全确认
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|---|---|---|---|---|
| SC10-1 | 创建 report | create |
rc == 0 | ✅ |
| SC10-2 | delete 输入 no → 取消 | delete stdin="no" |
输出包含"已取消" | ✅ |
| SC10-3 | info 验证仍存在 | info |
rc == 0 且名称匹配 | ✅ |
| SC10-4 | delete 输入 yes → 成功 | delete stdin="yes" |
删除成功 | ✅ |
| SC10-5 | info 验证已不存在 | info |
rc != 0 | ✅ |
场景 13: 边界 — functiongroup 不支持源码操作
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|---|---|---|---|---|
| SC13-1 | download functiongroup → 报错 | download --name ZIDT_MCP_TOOL --type functiongroup |
rc != 0 且包含"不支持" | ✅ |
| SC13-2 | sync functiongroup → 报错 | sync --name ZIDT_MCP_TOOL --type functiongroup |
rc != 0 且包含"不支持" | ✅ |
场景组 existing — 已有对象操作
场景 11: 已有对象查询
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|---|---|---|---|---|
| SC11-1 | info 已有 class | info --name ZCL_SAP_MCP_HANDLER --type class |
rc == 0 且名称匹配 | ✅ |
| SC11-2 | 下载已有 class | download --name ZCL_SAP_MCP_HANDLER |
rc == 0 且文件存在 | ✅ |
| SC11-3 | 同步无修改 → 成功 | sync(源码未修改) |
语法检查通过 + 激活成功 | ✅ |
场景组 config — 配置与环境
场景 12: 配置与环境
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|---|---|---|---|---|
| SC12-1 | 默认配置运行 | download(使用 config.ini) |
rc == 0 | ✅ |
| SC12-2 | 无效 SAP_HOST | 设置 SAP_HOST=http://invalid:9999 |
rc != 0 | ✅ |
测试统计
按场景组统计
| 场景组 | 场景数 | 步骤总数 | ✅ 通过 | ❌ 失败 | 通过率 |
|---|---|---|---|---|---|
| happy(正向完整流程) | 4 | 36 | 36 | 0 | 100% |
| error(逆向错误处理) | 3 | 11 | 11 | 0 | 100% |
| edge(边界场景) | 4 | 14 | 14 | 0 | 100% |
| existing(已有对象) | 1 | 3 | 3 | 0 | 100% |
| config(配置环境) | 1 | 2 | 2 | 0 | 100% |
| 合计 | 13 | 66 | 66 | 0 | 100% |
失败项分析
本次测试全部通过,无失败项。
测试过程中发现并修复的问题
| # | 问题 | 修复内容 |
|---|---|---|
| 1 | 下载文件含 \r\r\n 双重回车 |
保存前统一换行符 source.replace("\r\n", "\n") |
| 2 | lock() 未传 corrNr 导致写入冲突 |
lock() 增加 corr_nr 参数 |
| 3 | activate() 误判 inactiveObjects 为成功 |
新增 Content-Type 检测,正确处理 inactiveCtsObjects 响应 |
| 4 | activate() 使用 preauditRequested=true 导致激活失败 |
移除该参数 |
| 5 | _headers() 硬编码 Accept-Language: EN |
移除硬编码,使用 SAP 系统登录语言 |
| 6 | cmd_sync 交互式等待用户输入 |
改为非交互式,报错直接退出 |
| 7 | cmd_sync 缺少语法检查步骤 |
新增 syntax_check() 方法,激活前先做语法检查 |
| 8 | cmd_sync 对象不存在时直接退出 |
改为自动创建空对象后继续同步流程 |
| 9 | create 缺少 interface 默认模板 |
新增 interface 模板到 DEFAULT_TEMPLATES |
| 10 | parse_object_name 对 functiongroup 崩溃(src_uri 为 None) |
增加 if config["src_uri_template"] else None |
| 11 | cmd_info 缺少名称校验 |
新增查询结果名称与请求名称的对比校验 |
| 12 | cmd_info function 名称校验失败 |
用 split("/")[-1] 取实际对象名进行校验 |
| 13 | DDIC 对象 info 查询 Accept 头不正确 | 改用 */* Accept 头 |