Craft Agents 文档工具冒烟测试指南:基于 uv 的 CLI 工具验证体系解析
【免费下载链接】craft-agents-oss项目地址: https://gitcode.com/GitHub_Trending/cr/craft-agents-oss
导读
本文围绕 Craft Agents 仓库中apps/electron/resources/scripts/tests/目录下的文档工具冒烟测试(Document Tool Smoke Tests)展开,说明该测试目录如何对随 Electron 应用打包的 8 个 CLI 文档工具(PDF、XLSX、DOCX、PPTX、图片、iCalendar、文档差异、MarkItDown)进行端到端验证。读完本文,你将掌握这些测试的运行方式、测试基础设施(共享 harness)的实现原理、wrapper 二进制与 uv 内联脚本的调用链,以及如何在修改工具脚本后补充相应的冒烟测试。
测试目录的定位与设计初衷
冒烟测试(Smoke Test)的核心目标是"快速验证关键路径可用",而不是穷尽边界行为。Craft Agents 将随应用分发的文档处理 CLI 工具作为打包资产,因此需要在持续集成中确认每个工具在真实环境下能够正常启动、执行核心子命令并产生预期输出。
该目录位于 apps/electron/resources/scripts/tests/,测试对象是 apps/electron/resources/scripts/ 下的工具脚本。与常规单元测试直接调用函数不同,这里的冒烟测试执行的是resources/bin/*下的 wrapper 二进制(而非直接运行脚本),从而完整覆盖"打包 → 分发 → 通过 wrapper 启动"这条真实使用链路。
从测试目录看,共包含 8 个冒烟测试模块与 1 个共享测试基础设施:
| 测试模块 | 对应 wrapper | 覆盖的工具脚本 |
|---|---|---|
test_pdf_tool_smoke.py | pdf-tool | pdf_tool.py |
test_xlsx_tool_smoke.py | xlsx-tool | xlsx_tool.py |
test_docx_tool_smoke.py | docx-tool | docx_tool.py |
test_pptx_tool_smoke.py | pptx-tool | pptx_tool.py |
test_img_tool_smoke.py | img-tool | img_tool.py |
test_ical_tool_smoke.py | ical-tool | ical_tool.py |
test_doc_diff_smoke.py | doc-diff | doc_diff.py |
test_markitdown_smoke.py | markitdown | markitdown_cli.py |
运行全部冒烟测试
原文档提供了两种运行方式,均从仓库根目录执行。
方式一:直接使用python3 -m unittest指定全部测试模块
python3 -m unittest \ apps.electron.resources.scripts.tests.test_pdf_tool_smoke \ apps.electron.resources.scripts.tests.test_xlsx_tool_smoke \ apps.electron.resources.scripts.tests.test_docx_tool_smoke \ apps.electron.resources.scripts.tests.test_pptx_tool_smoke \ apps.electron.resources.scripts.tests.test_img_tool_smoke \ apps.electron.resources.scripts.tests.test_ical_tool_smoke \ apps.electron.resources.scripts.tests.test_doc_diff_smoke \ apps.electron.resources.scripts.tests.test_markitdown_smoke方式二:使用根目录脚本(推荐)
bun run test:doc-tools根目录 package.json 中的脚本定义如下,它本质上就是把方式一的 8 个模块拼接到一条python3 -m unittest命令中:
"test:doc-tools": "python3 -m unittest apps.electron.resources.scripts.tests.test_pdf_tool_smoke apps.electron.resources.scripts.tests.test_xlsx_tool_smoke apps.electron.resources.scripts.tests.test_docx_tool_smoke apps.electron.resources.scripts.tests.test_pptx_tool_smoke apps.electron.resources.scripts.tests.test_img_tool_smoke apps.electron.resources.scripts.tests.test_ical_tool_smoke apps.electron.resources.scripts.tests.test_doc_diff_smoke apps.electron.resources.scripts.tests.test_markitdown_smoke"值得注意的是,test:doc-tools已经被纳入统一的开发校验流程validate:dev(见 package.json),该流程依次执行类型检查、共享包测试与文档工具冒烟测试,说明这套冒烟测试是仓库质量门禁的一部分。
运行单个测试套件
当只修改了某一个工具(例如 XLSX 工具)时,可以只运行对应的套件以缩短反馈回路:
python3 -m unittest apps.electron.resources.scripts.tests.test_xlsx_tool_smoke同样地,8 个模块均遵循apps.electron.resources.scripts.tests.test_<tool>_smoke的命名约定,任何工具都可以用同样的方式单独运行。
共享测试基础设施_tool_test_harness.py解析
所有测试套件都依赖同一个共享模块 apps/electron/resources/scripts/tests/_tool_test_harness.py。它把"如何定位脚本、如何选择 uv、如何构建环境变量、如何执行 wrapper"这些公共逻辑收敛到一处,避免 8 个测试文件各自重复实现。
路径定位
harness 通过Path(__file__).resolve().parents[5]从测试文件自身向上回溯得到仓库根目录(REPO_ROOT),并据此推导出两个关键目录:
BIN_DIR = REPO_ROOT / "apps" / "electron" / "resources" / "bin":wrapper 二进制目录SCRIPTS_DIR = REPO_ROOT / "apps" / "electron" / "resources" / "scripts":工具脚本目录
由于路径从文件位置推导而非依赖环境变量,测试可以在任意工作目录下运行,只要仓库结构完整。
平台键解析与 uv 选择
resolve_platform_key()负责把当前运行环境归一化为{os}-{arch}形式的键(如darwin-arm64、linux-x64、win32-x64),用于定位BIN_DIR下对应平台的捆绑uv。
resolve_uv_binary()的解析顺序是:
- 优先使用捆绑的
BIN_DIR/<platform_key>/uv(Windows 上为uv.exe); - 若捆绑 uv 缺失,回退到系统
PATH上的uv(通过shutil.which); - 两者都不可用时抛出
FileNotFoundError,并明确指出"既没有捆绑 uv 也没有 PATH 上的 uv"。
这正是原文档 Notes 中"若捆绑的uv在你的平台缺失,harness 回退到 PATH 上的uv"这一行为的源码实现。
wrapper 解析与执行
resolve_wrapper(tool_name)在BIN_DIR中查找同名 wrapper,Windows 平台追加.cmd后缀。run_tool(tool_name, *args)则完成"构建环境 → 解析 wrapper → 以仓库根目录为工作目录执行"的完整流程,并通过capture_output=True捕获 stdout/stderr 供测试断言。
build_env()是环境准备的核心,它设置三个关键环境变量:
CRAFT_UV:指向解析出的 uv 可执行文件绝对路径;CRAFT_SCRIPTS:指向工具脚本目录;PATH:把BIN_DIR与uv.parent前置插入,确保 wrapper 及其依赖可被发现。
wrapper 二进制与 uv 内联脚本的调用链
以 DOCX 工具为例,apps/electron/resources/bin/docx-tool 的完整内容是一个极简的 POSIX shell 脚本:
#!/bin/sh exec "$CRAFT_UV" run --python 3.12 "$CRAFT_SCRIPTS/docx_tool.py" "$@"它把参数原样透传给docx_tool.py,形成"测试 → wrapper → uv → 内联脚本"的完整调用链。工具脚本本身采用 PEP 723 内联脚本元数据声明依赖,例如 apps/electron/resources/scripts/xlsx_tool.py 头部声明:
# /// script # requires-python = ">=3.12" # dependencies = ["openpyxl>=3.1,<4", "click>=8.3,<9"] # ///uv 会根据这份元数据自动创建隔离环境并解析依赖,这正是 wrapper 中exec "$CRAFT_UV" run能开箱即用的原因。类似地,pdf_tool.py 声明了pypdfium2、pypdf、img2pdf、Pillow、click、python-pptx、python-docx等依赖,doc_diff.py 则依赖markitdown、python-docx、diff-match-patch、click,并显式屏蔽了 markitdown 的 ffmpeg 缺失告警。BIN_DIR下每种工具都同时提供无后缀的 shell 脚本与.cmd(Windows)两个变体(如docx-tool/docx-tool.cmd、pdf-tool/pdf-tool.cmd),保证跨平台可用。
各套件冒烟覆盖点:从源码看测试意图
8 个套件都遵循setUpClass中构建夹具(fixture)、测试方法中执行工具并断言、tearDownClass中清理临时目录的同一模式。以下是各套件的核心覆盖点。
PDF 工具(test_pdf_tool_smoke.py)
test_pdf_tool_smoke.py 的夹具构建展示了工具之间的协作:先用img-tool resize把 1×1 透明 PNG 放大为 200×200 的三张图,再用pdf-tool from-image合成输入 PDF。其断言重点放在"加固行为"上:
extract --pages 999越界页必须失败且报out of bounds;reorder --order 1,2 --reverse冲突参数必须失败且报mutually exclusive;duplicate --copies 1低于下限必须失败且报x>=2;to-pptx --pages 999越界选择必须优雅失败,且不能在 stderr 中出现IndexError原始堆栈;sanitize正常路径必须返回 0 且输出文件存在。
这与 pdf_tool.py 文档字符串中的说明一致:页范围解析是严格的,越界页与畸形--pages输入现在会返回显式错误而不是被静默忽略。
XLSX 工具(test_xlsx_tool_smoke.py)
test_xlsx_tool_smoke.py 覆盖write(含--type number写入数字 42)、info(sheet_count ≥ 1)、read --format json(校验行内容与类型)、export --format csv(输出文件存在)、add-sheet(新 sheet 出现在 info 输出中),并验证读取不存在的 sheet 时报not found。其中 JSON 读取断言rows[0]["name"] == "alice"且rows[0]["score"] == 42,说明read的 JSON 输出保留了首行作为表头并正确处理了数值类型。
DOCX 工具(test_docx_tool_smoke.py)
test_docx_tool_smoke.py 串起一条完整的"创建 → 提取 → 模板填充 → 文本替换"工作流:create --text "# Report\n\nHello **world**"生成文档后,extract能取回标题与正文;template --data '{"name":"Balint"}'完成{{name}}占位符填充;replace --find Balint --replace-with "Craft Agent"完成文本替换并可通过再次extract验证。负向用例验证传入非法 JSON({not-json})时失败并报Error parsing JSON。
PPTX 工具(test_pptx_tool_smoke.py)
test_pptx_tool_smoke.py 用create --text配合---分页符生成多页幻灯片,断言info中slide_count >= 2、extract能取回各页文本;负向用例验证extract --slide 99越界时报out of range。
图片工具(test_img_tool_smoke.py)
test_img_tool_smoke.py 覆盖info(stdout 输出 JSON 且format == "PNG")、resize --scale 2、convert --format jpg的完整链路;负向用例验证resize --scale 0失败并报must be positive。
iCalendar 工具(test_ical_tool_smoke.py)
test_ical_tool_smoke.py 以 JSON 数组形式传入事件数据调用create --data生成.ics文件,随后read --format json校验event_count与事件summary,filter --start/--end按时间窗过滤;负向用例用not-a-valid-ics验证畸形文件读取时 stderr 含Error:。
文档差异工具(test_doc_diff_smoke.py)
test_doc_diff_smoke.py 验证doc-diff对两个文本文件执行--format summary时输出包含Comparison:与Similarity:字样;负向用例验证输入文件不存在时失败并报does not exist。结合 doc_diff.py 的实现可知,该工具先把两个文档经 markitdown 转换为 Markdown,再用diff-match-patch计算并展示差异。
MarkItDown 工具(test_markitdown_smoke.py)
test_markitdown_smoke.py 覆盖两条路径:纯文本文件直接透传(stdout 含hello craft),以及 DOCX 回退路径——先用docx-tool create生成.docx,再验证markitdown能将其转换为 Markdown 并取回正文文本。
临时夹具的自动创建与清理
原文档 Notes 中"测试在运行时创建临时夹具并自动清理"的机制,在各套件中体现为setUpClass/tearDownClass配合tempfile.TemporaryDirectory:
cls.tmpdir_obj = tempfile.TemporaryDirectory(prefix="xlsx-tool-smoke-") cls.tmpdir = Path(cls.tmpdir_obj.name)每个套件使用带工具名的前缀(如pdf-tool-smoke-、img-tool-smoke-、doc-diff-smoke-)创建独立临时目录,所有输入输出都限定在该目录内;tearDownClass调用cleanup()自动删除整个目录,既保证测试互不干扰,也不会在仓库中残留产物。这种方式也让冒烟测试可以放心地写文件、生成 PDF/PPTX/XLSX 等二进制产物,而不必担心污染源码树。
修改工具后如何补充冒烟测试
原文档的 Contributor expectation 明确指出:如果你修改了apps/electron/resources/scripts/下的任何脚本,或apps/electron/resources/bin/下的 wrapper,就必须在本目录更新或新增相应的冒烟测试。实践上可遵循以下流程:
- 定位对应套件:根据修改的工具脚本在
tests/目录中找到同名test_<tool>_smoke.py,例如改xlsx_tool.py则更新test_xlsx_tool_smoke.py; - 从 harness 引入工具:通过
from ._tool_test_harness import build_env, run_tool复用环境构建与 wrapper 执行逻辑,并在setUpClass中调用build_env()获取环境; - 补充正向与负向断言:参考现有模式,正向用例验证返回码为 0 且输出符合预期(文件存在 / stdout 包含关键字 / JSON 字段正确),负向用例验证非法输入返回非 0 且 stderr 含明确的错误提示(如
out of bounds、must be positive、does not exist); - 单独跑通再全量验证:先
python3 -m unittest apps.electron.resources.scripts.tests.test_<tool>_smoke快速迭代,最后执行bun run test:doc-tools确认不破坏其他工具套件。
小结
Craft Agents 的文档工具冒烟测试是一套围绕"打包分发链路"设计的最小化端到端验证体系:通过共享 harness 统一处理平台键解析、uv 选择、wrapper 定位与环境变量注入,让 8 个工具套件以一致的方式经由真实 wrapper 二进制执行;通过内置的负向用例验证工具的"加固行为"(越界、冲突参数、非法输入都能显式报错而非抛出原始堆栈);通过临时夹具的自动创建与清理保证测试可重复、无残留。对于任何修改文档工具脚本或 wrapper 的贡献者,本目录都是必须同步维护的测试阵地。
【免费下载链接】craft-agents-oss项目地址: https://gitcode.com/GitHub_Trending/cr/craft-agents-oss
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考