测试指南(Testing Guide)

June 19, 2026 · View on GitHub

面向所有协作者(含非专业用户)的测试地图:测什么、怎么一键跑、新功能往哪加测试。 主入口:CLAUDE.md §测试分层与 AI 选命令规则。 配套:update-rules.md(验收清单)、testing-rebuild-handoff.md(浏览器测试体系修复交接)、CATALOG.md(文档索引)。

URDF Studio 采用大厂常见的测试金字塔分层:底层多而快、顶层少而慢。理解这三层,就知道任何改动该跑哪个命令、该补哪种测试。

        ╱ L3 语料/真值回归 ╲      少、最慢、最接近真实(真实机器人语料 + golden 对比)
      ╱────────────────────╲
    ╱   L2 浏览器 E2E 测试   ╲    中等数量、慢(真启浏览器,按功能点过用户路径)
  ╱──────────────────────────╲
╱      L1 单元测试(536+)      ╲  最多、最快(纯 Node,毫秒级,逻辑/边界)
────────────────────────────────

L1 · 单元测试(最常用)

  • 是什么:纯 Node(node:test + node:assert/strict)测试,不启浏览器、毫秒级。覆盖解析器、store、hooks、工具函数的逻辑与边界。
  • 放在哪:紧挨源码,src/**/*.test.ts(约 538 个)。新写的工具/纯逻辑就在它旁边建 xxx.test.ts
  • 怎么跑
目的命令
默认快速冒烟(CI 用)npm test
跑全部单元测试npm run test:unit:all
只跑某个文件npm run test:unit -- src/core/robot/builders.test.ts
看有哪些 suite / 各有多少文件npm run test:unit:list
  • runner:scripts/test/runner/run-node-tests.mjs(管理 fast / src / all 等 suite)。

L2 · 浏览器端到端(E2E)测试

  • 是什么:用 Puppeteer 真启一个 headless 浏览器,按功能走一遍用户路径(导入模型 → 操作 → 断言)。这是"碰撞编辑、测量、拼接、导入导出、AI、主题、显示开关…"这类用户可见功能的测试层。
  • 放在哪scripts/test/browser/test_*.mjs,每个文件对应一个功能;当前覆盖 30 个浏览器用例(含 7 个 MuJoCo/MJCF 专项)。共用地基:
    • scripts/test/helpers/browser-helpers.mjs —— 启服务器/浏览器、文件上传、加载触发、稳定化。
    • scripts/test/helpers/assertions.mjs —— createTestSuite / assert* / printSummary
    • scripts/test/browser/helpers/<格式>-helpers.mjs —— 各格式(urdf/mjcf/sdf/usd/xacro)的 importModel,封装"上传 + 选中 + 加载"。
  • 怎么跑
目的命令
单个功能npm run test:browser:theme(或 :mujoco-import / :measure / :collision-opt / :urdf-import …)
全部浏览器测试npm run test:browser:all(内部调用 run-all --browser-only,自动发现全部 test:browser:*
看有哪些node scripts/test/runner/run-all.mjs --list
  • 关键机制:测试通过 URL 上的 ?regressionDebug=1 暴露 window.__URDF_STUDIO_DEBUG__ 调试接口(默认关闭,见 CLAUDE.md 红线)。helper 会自动加这个参数。
  • 跑完务必清理(CLAUDE.md 红线):node test/usd-viewer/scripts/cleanup-headless.cjs

L3 · 语料 / 真值回归(最慢、最真实)

  • 是什么:拿真实机器人语料(MuJoCo menagerie、Unitree、Gazebo)批量导入/导出,与预先生成的 golden/truth 数据对比。还有性能 benchmark。
  • 怎么跑npm run test:fixtures(聚合)或单项 test:fixtures:*。需要 test/ 下的大型语料。
  • 语料从哪来npm run test:setup(克隆全部)或单项 test:setup:*。脚本在 scripts/test/setup/,幂等(已存在则跳过)。

"一口气全跑完":统一入口 run-all

scripts/test/runner/run-all.mjs 把三层串起来跑,某个失败不会中断全局,最后打印一张通过/失败总表并写入 tmp/regression/run-all-summary.json。它会先起一个共享 dev server,让所有浏览器测试复用、不必各自冷启动 Vite。

npm run test:all                                     # 便捷别名 = run-all.mjs(单元 + 全部浏览器)
node scripts/test/runner/run-all.mjs              # 单元 + 全部浏览器(默认)
node scripts/test/runner/run-all.mjs --unit-only  # 只跑单元
node scripts/test/runner/run-all.mjs --browser-only --filter export   # 只跑名字含 export 的浏览器测试
node scripts/test/runner/run-all.mjs --fixtures   # 额外带上 L3 语料回归
node scripts/test/runner/run-all.mjs --headed     # 显示浏览器窗口(调试用)
node scripts/test/runner/run-all.mjs --list       # 只列出将要跑的阶段

浏览器层整体较慢(每个用例都要导入模型、构建 3D 场景)。日常开发用 npm test(L1)即可;提交大改动或发版前再 run-all

现成的快速/完整流水线(不含浏览器层):npm run verify:fast(格式+lint+typecheck:quality+L1+构建)、npm run verify:full(再加 L3 fixtures)。


新增功能时,该往哪一层加测试?

  1. 纯逻辑 / 工具函数 / store action / 解析器:加 L1 单元测试(src/**/*.test.ts),最优先,便宜又快。
  2. 用户能在界面上操作的功能(点按钮、拖拽、切换、导入导出):加 L2 浏览器测试。
    • scripts/test/browser/ 仿照同类 test_*.mjs 新建一个文件;
    • createSession 起会话、对应格式 helper 的 importModel 导入模型、store.* 操作、assert* 断言、printSummary 收尾;
    • package.json 加一条 test:browser:<name>run-all.mjs 会自动发现它(无需改 run-all)。
  3. 涉及真实模型解析/导出正确性:加 L3 语料回归(scripts/test/truth/)。

判断原则:能用 L1 覆盖的逻辑就别用 L2(快、稳、便宜);只有"必须真跑浏览器才能验证"的交互/渲染才上 L2。

读懂结果

  • 单元测试:末尾 pass / fail 计数,退出码非 0 即失败。
  • 浏览器测试:每条断言一行 [suite:<名字>] ✓/✗ ...,结尾 [summary:<名字>] passed/failed
  • run-all:总表 + tmp/regression/run-all-summary.json;每个浏览器用例还各自写 tmp/regression/<name>_results.json