
cli-anything-sbox 测试体系全解析244 项测试的分层设计、退出码契约与地图测试编排【免费下载链接】CLI-AnythingCLI-Anything: Making ALL Software Agent-Native -- CLI-Hub: https://clianything.cc/项目地址: https://gitcode.com/GitHub_Trending/cl/CLI-AnythingsboxFacepunch Studios 基于 Source 2 的游戏引擎的 CLI 自动化桥接层 cli-anything-sbox 在 sbox/agent-harness 中内置了一套 244 项测试的完整测试体系。本文以 sbox/agent-harness/cli_anything/sbox/tests/TEST.md 为主线从测试清单、运行方式、逐类覆盖、模块映射到已知边界与平台怪癖逐层拆解这套测试的设计意图并配合 sbox_cli.py 与各 core 模块源码验证其底层契约。读完本文你将掌握如何在无 sbox 安装的环境下跑通纯单元与子进程级 E2E 测试、如何配置环境变量切换测试目标、理解一次性命令失败必须退出码 1这一对 Agent 自动化至关重要的契约以及地图生成测试编排管线的组合矩阵与哨兵轮询机制。1. 测试清单总览整个测试套件集中在 sbox/agent-harness/cli_anything/sbox/tests 目录下由四个测试文件构成全部可在agent-harness/目录下运行测试文件覆盖范围测试数test_core.py全部 13 个 core 模块project、scene、prefab、codegen、input_config、collision_config、material、sound、localization、session、export、validate外加 sbox 后端解析器157test_full_e2e.py通过子进程调用 CLI 表层、项目工作流端到端、sbox_backend 集成50test_orchestrator.py地图生成测试编排器组合矩阵、哨兵轮询、RGBA→PNG 转换、配置读写17test_exit_codes.py一次性 CLI 退出码契约、REPL 吸收 SystemExit、monkeypatch 覆盖的字典返回失败路径20合计244值得注意的是 test_core.py 的 157 项测试来自 36 个测试类是纯单元测试而 test_full_e2e.py 的 40 项TestCLISubprocess测试并不依赖 sbox 安装它们通过子进程调用 CLI 并断言--json输出专门验证 README.md 中记载的 79 个命令、14 个命令组的可用性。2. 运行测试分层命令与环境配置2.1 按粒度运行的命令从agent-harness/目录执行对应 sbox/agent-harness/setup.py 的包布局# 纯单元测试速度快无需安装 sbox python -m pytest cli_anything/sbox/tests/test_core.py -v # 编排器测试Pillow 为硬依赖通常可直接运行 python -m pytest cli_anything/sbox/tests/test_orchestrator.py -v # 完整 E2E 套件TestE2EBackend 在未安装 sbox 时自动跳过 python -m pytest cli_anything/sbox/tests/test_full_e2e.py -v # 全部测试 python -m pytest cli_anything/sbox/tests/ -v2.2 环境变量契约变量作用SBOX_PATH将 harness 指向非 Steam 目录的 sbox 安装。当 sbox 不在标准 Steam 库中时TestE2EBackend必须依赖它。CLI_ANYTHING_FORCE_INSTALLED设为1/true/yes时TestCLISubprocess改为针对已安装的cli-anything-sbox控制台脚本运行而不是python -m。这两个变量的行为与 test_full_e2e.py 中的_resolve_cli辅助函数直接对应它优先用shutil.which查找已安装命令找不到时回退到python -m cli_anything.sbox.sbox_cli只有当CLI_ANYTHING_FORCE_INSTALLED被设置为真值且命令不在 PATH 中时才会抛错。这套先探测、后回退策略保证了测试套件在只有源码、未做pip install -e .的裸环境下也能跑。2.3 跳过Skip行为TestE2EBackend3 项测试在主机上找不到 sbox 时整体跳过——不是失败、不是报错而是带原因说明的干净跳过。test_converts_rgba_bytes_to_png在 Pillow 不可导入时跳过由于 Pillow 是硬依赖正常情况下始终可导入。这种环境不满足就跳过而非报错的设计使套件可以在仅具备 Python 的开发机、未安装引擎的 CI 以及完整 sbox 环境中三态运行。3. test_core.py157 项单元测试36 个类test_core.py 是套件的主体覆盖 13 个 core 模块的全部纯逻辑。所有测试自包含文件操作统一使用 pytest 的tmp_pathfixture见文件头部注释不触碰真实用户目录。以下按被测模块分组说明。3.1 项目与校验project / validate类测试数被测模块与要点TestProject7core/project.pycreate、load/save、info、configure、find_sbprojTestProjectPackages4add/remove package referencesTestProjectValidate4core/validate.py坏引用、重复 GUID、畸形输入从源码签名看core/project.py 的create_project支持name、project_type默认game、org默认local、max_players默认 64、tick_rate默认 50、network_type默认Multiplayer、startup_scene默认scenes/minimal.scene等参数_default_sbproj与_default_minimal_scene负责生成工程骨架。validate 模块的validate_project(project_dir, check_refs, check_guids, check_inputs)是项目体检入口由_build_asset_index、_is_engine_builtin、_collect_guids等内部函数配合完成引用与 GUID 检查。3.2 场景scene最大的一簇共 50 项类测试数要点TestScene12create、list、增删 object/component、find、GUID 唯一性TestSceneQuery9query_objects按 component / tag / name / regex / bounds / enabled / 组合过滤TestSceneRefs3extract_asset_refs默认场景、去重排序、空场景TestSceneBulkModify4bulk_modify_objects位置、多字段、无匹配、需更新TestSceneCloneAndGet6clone_objectget_objectTestSceneModify4modify_objectname、position、scale、tagsTestSceneModifyComponent2modify_component_propertiesTestSceneInstantiatePrefab5instantiate_prefab默认名、覆盖名、prefab 来源引用、父对象、非法父对象TestSceneDiff5diff_scenes相同、增删、位置、组件、场景属性从 core/scene.py 的符号清单可见其实现厚度query_objects支持has_component、has_tag、name_match、name_regex、in_bounds、enabled六个过滤维度内部通过_object_has_component、_object_has_tag、_parse_position_bounds、_object_in_bounds等谓词实现 AND 组合语义diff_scenes依赖_diff_two_objects与_object_summary生成结构化差异extract_asset_refs通过_walk_for_refs递归遍历 JSON 节点识别.vmdl/.vmat/.vsnd/.vtex/.vpcf/.prefab等资源引用并调用_is_asset_ref与_category_for_ref分类。3.3 预制体prefab14 项类测试数要点TestPrefab4create、带组件、info、from-sceneTestPrefabComponents2在 prefab 根上增删组件TestPrefabRefs1extract_asset_refsTestPrefabModifyComponent5按 type / 按 guid 修改组件、未找到、缺 id、缺 propertiesTestPrefabDiff2diff_prefabs相同、根变更from_scene_object(scene_path, object_guid, output_path)core/prefab.py是从场景实体抽出预制体的关键函数与场景侧的instantiate_prefab形成双向转换闭环。3.4 代码生成codegen22 项类测试数要点TestCodegen10组件基础、带属性、网络化、接口、生命周期方法、gameresource、编辑器菜单、代码风格tabs / Allman / CRLFTestCodegenRazor5generate_razorTestCodegenClass3generate_class静态类、基类、多行方法体回归TestCodegenPanelComponent5基础、含 ScreenPanel、命名空间、属性、GUID 唯一性core/codegen.py 提供generate_component支持lifecycle_methods、interfaces、is_networked、rpc_methods、generate_gameresource、generate_editor_menu、generate_razor、generate_panel_component、generate_class六个生成器内部由_format_property、_format_method、_format_rpc_method等函数负责逐行格式化。网络化组件与 RPC 方法、Razor UI、PanelComponentScreenPanel 双件套这些易错点都有专门测试钉住输出格式。3.5 输入与碰撞配置input_config / collision_config12 项类测试数要点TestInputConfig6取默认、添加、重复、删除、设置、列出core/input_config.pyTestCollisionConfig5取默认、加层、删内置层、加规则、删规则TestCollisionRemoveLayer1remove_layeradd_action(name, groupOther, keyboard_codeNone, gamepad_codeNone, titleNone)与add_layer(name, defaultCollide)、add_rule(layer_a, layer_b, resultCollide)的默认参数均在单元测试中被逐一验证。3.6 材质 / 声音 / 本地化19 项类测试数要点TestMaterial5new、load/save、info、list、presetscore/material.pyTestMaterialUpdate2update_materialshader、颜色贴图TestSound4new、info、list、presetscore/sound.pyTestSoundUpdate2update_sound_eventTestLocalization5new、set、get、list、removecore/localization.pyTestLocalizationBulkSet1bulk_setcreate_material的默认参数shadercomplex、metalness0.0、tint1 1 1 0、create_sound_event的默认参数volume1、pitch1、decibels70、selection_modeRandom、occlusionTrue都在这一层被固定下来。3.7 会话 / 导出 / 引用图30 项类测试数要点TestSession6create、set project、undo/redo、save/load、clear、损坏文件警告 时间戳备份core/session.pyTestExport3列资源、过滤列表、查找工程目录TestAssetRefGraph5find_asset_refsfind_unused_assetsTestAssetRenameMove7rename_assetmove_asset均含 dry-run、目标已存在、源缺失、跨目录core/export.py 的rename_asset/move_asset是重命名/移动资源并同步更新所有场景与预制体引用的高危操作测试对 dry-run、目标冲突、跨目录三种边界都做了覆盖_rewrite_string_refs与_rewrite_refs_in_project负责在项目内重写引用字符串。3.8 组件预设presets3 项类测试数要点TestComponentPresets2COMPONENT_PRESETS数量与标准名称TestJointPresets1关节预设覆盖sandbox的COMPONENT_PRESETS常量位于 core/scene.py被_resolve_component_type使用用于把--components中的短名如box_collider、rigidbody解析为完整组件类型。README 中记载的 29 个预设model、box_collider、sphere_collider、rigidbody、camera、各类灯光、joint 系列等即由此常量驱动。4. test_full_e2e.py50 项端到端测试3 个类4.1 TestCLISubprocess40 项以子进程方式启动cli-anything-sbox未安装时回退为python -m cli_anything.sbox.sbox_cli对 README 记载的每个命令组断言--json输出结构。它验证的是命令真实可执行、输出可被机器解析而非仅仅存在于 help 文本中。4.2 TestE2EProjectWorkflow7 项使用进程内 Click runner 跑完整项目工作流。test_full_project_creation见 test_full_e2e.py是典型代表它断言一次create_project必须同时产出.sbproj文件且为合法 JSONTitle、Type字段正确Assets/scenes/minimal.scene含GameObjects与SceneProperties两个键Code/Assembly.cs与Editor/Assembly.csProjectSettings/Input.config与ProjectSettings/Collision.config且均为合法 JSON。test_scene_manipulation_workflow则验证 create → add 3 个对象不同组件组合→ 校验对象计数与 GUID 互异的完整链路。整个工作流覆盖 create → add objects → generate code → validate 的闭环。4.3 TestE2EBackend3 项真实 sbox 后端集成覆盖 utils/sbox_backend.py 的三个探测函数find_sbox_installation定位安装、get_sbox_version读取版本、find_server_executable定位服务端可执行文件。未安装 sbox 时这 3 项干净跳过。5. test_orchestrator.py17 项地图测试编排器测试这组测试验证 core/test_orchestrator.py地图生成测试管线的五个核心机制类测试数覆盖TestComboMatrix7Strategy × Size × Seed 组合矩阵TestSentinelPolling3文件系统哨兵轮询用于检测引擎内测试是否完成TestConfigIO3test_config.json往返读、校验、写TestRgbaConversion2RGBA 字节数组 → PNG经 PillowTestDataPathResolution2FileSystem.Data路径在编辑器与独立模式下的解析从测试断言可以看出组合矩阵的默认形态build_combo_matrix()默认产生 12 个组合即 4 种策略 × 3 种尺寸 × 1 个种子策略集合为{Serpentine, Gilbert, SpanningTree, Backbite}尺寸集合为{Small, Medium, Large}。seeds[42, 99]时矩阵扩为 24 项seed_count3时扩为 36 项策略/尺寸均可单独过滤——这意味着管线可以按需组合出任意规模的测试矩阵。哨兵机制中check_sentinel读取test_complete.json存在则返回其内容含success/error字段不存在返回Nonepoll_for_sentinel在此基础上提供 60 秒默认超时。rgba_to_png则由screenshot.rgba原始像素缓冲转换为 PNG 输出。run_test_pipeline是整个编排的入口遍历组合矩阵 →swap_startup_scene切换启动场景 → 以run_single_combo逐个启动 sbox →collect_screenshot收集截图 →poll_for_sentinel等待引擎内测试完成哨兵 →cleanup_data_files清理test_config.json、screenshot.rgba、metadata.json、test_complete.json等中间产物。6. test_exit_codes.py20 项退出码契约测试这组测试是本套件对 Agent 自动化最关键的贡献其背景记录在文件 docstring 中HKUDS/CLI-Anything PR #251 的评审反馈指出handler 捕获异常后用_output_error()打印、随后正常返回导致脚本和 Agent 把失败误判为成功。这组测试把契约钉死一次性模式下任何失败命令必须以退出码 1 结束成功路径保持退出码 0REPL 模式则吸收退出让循环继续。6.1 五个测试类类测试数覆盖TestOneShotFailureExitsNonZero11失败路径返回退出码 1场景/工程文件缺失人读 JSON 双模式、本地化 key 缺失、必填参数缺失、--properties传入非法 JSON、无 sbox 安装时执行 asset compile、缺失文件上的 scene listexcept Exception裸路径的真门禁、损坏 JSON 上的 asset infojson_info.error表层。含--help退出码 0 基线。TestOneShotSuccessExitsZero2确认退出码 1 改动没有回归成功路径TestProjectValidateExitsNonZero2project validate在okFalse坏引用时退出 1 以便 CI 把关干净工程仍退出 0TestAuditedFailurePaths2monkeypatch 覆盖字典返回型失败路径resourcecompiler 返回successFalse的asset compile、get_sbox_version()带error字段的server infoTestReplModeAbsorbsExit3_output_error在ctx.obj[repl] True时不调用sys.exit键缺失时安全默认一次性模式6.2 底层实现契约的实现在 sbox_cli.py 的_output_errorJSON 模式在 stdout 输出{error: message}人读模式在 stderr 输出Error: ...随后仅当ctx.obj[repl]为假时sys.exit(1)。集中式退出逻辑让每个命令的 try/except 保持简洁同时统一传播失败。TestAuditedFailurePaths之所以需要 monkeypatch是因为asset compile失败resourcecompiler 返回successFalse和server info版本读取失败返回error字段这两条路径返回的是字典而非抛异常标准的except Exception路径抓不到审计补上了显式 success/error 检查测试用CliRunner monkeypatch组合把该契约钉死断言输出含 Resource compilation failed 与 Failed to read sbox version。6.3 子进程测试的可移植性测试用python -m cli_anything.sbox而非安装后的cli-anything-sbox二进制是为了在启动脚本行为可能不同的环境中保持可移植如 Windows Python 3.14 的原生 launcher 怪癖_run辅助函数统一传入stdinsubprocess.DEVNULL规避平台句柄问题详见第 8 节。7. 按源码模块的覆盖矩阵TEST.md 给出了模块维度的三层覆盖单元 / E2E / 子进程此处是理解测试密度分布的关键模块单元E2E子进程core/project.py7 4 packages2创建、塔防1project newcore/scene.py5012934642553操作、塔防、工作流6query、refs、bulk-modify×2、diff×2、instantiatecore/prefab.py14421521from_scene3refs、modify-component、diffcore/codegen.py22105251codegen 工作流3panel-component 普通 scene 追加core/input_config.py62配置工作流、塔防1input listcore/collision_config.py6512配置工作流、塔防1collision listcore/material.py752--core/sound.py642--core/localization.py651--core/session.py5--core/export.py15357-5find-refs、find-unused、rename、rename-dry-run、movecore/validate.py4-2validate 干净、validate 损坏core/test_orchestrator.py17整文件--utils/sbox_backend.py-3安装、版本、可执行文件无 sbox 时跳过-sbox_cli.pyCLI--40TestCLISubprocess 全部 20退出码契约 audited 路径 monkeypatch REPL 吸收这张矩阵揭示了一个清晰的分层策略纯文件逻辑material/sound/localization/session/export/validate以单元测试为主涉及引擎进程sbox_backend、resourcecompiler的部分以可跳过的 E2E 或 help-smoke 为主而 CLI 表层则由 40 项子进程测试全量把守。8. E2E 前置条件与已知测试环境怪癖8.1 E2E 前置条件TestCLISubprocess40 项与TestE2EProjectWorkflow7 项在无 sbox 安装时即可运行——它们针对内存态或临时目录的项目状态执行 CLI。TestE2EBackend3 项要求 sbox 可被发现标准 Steam 安装无需任何操作非标准位置需export SBOX_PATH/path/to/sbox未安装则带原因说明跳过。8.2 已知怪癖Windows Python 3.14 pytest 的句柄问题subprocess.Popen在子进程启动前可能抛OSError: [WinError 6] The handle is invalid原因是父进程 stdin 句柄不可继承。test_full_e2e.py 的TestCLISubprocess._run与 test_exit_codes.py 的_run都通过stdinsubprocess.DEVNULL规避。新增子进程调用时必须照此模式。显式 UTF-8所有文件 I/O 均显式指定open(..., encodingutf-8)。早期版本在 Windows 上默认平台编码提交上游前已修复——这也是所有测试断言文件内容时统一使用 UTF-8 的原因。9. 覆盖缺口诚实声明的边界TEST.md 明确列出了三处未覆盖区域理解它们有助于正确解读测试结论server start与launch命令仅经 help 文本冒烟测试真正启动 sbox 服务端/编辑器进程超出范围需要活网络套接字与运行中的游戏。session undo/redo仅在单元层测试没有 E2E 工作流在编辑-撤销-再编辑的真实序列中走一遍撤销日志。asset compile仅 help 测试调用真实resourcecompiler.exe需要完整 sbox 安装留给上游维护者的 CI。缺失/失败编译的非零退出码路径由test_exit_codes.py::test_asset_compile_missing_sbox_exits_one覆盖。10. 总结这套测试教你如何为 Agent 原生 CLI 设计质量门槛从 sbox harness 的 244 项测试可以提炼出可复用的测试设计模式三层粒度纯单元fast、无外部依赖→ 子进程级 CLI 冒烟验证命令真实可执行且 JSON 可解析→ 引擎集成条件不满足时干净跳过让不同 CI 环境各取所需退出码即契约一次性模式下失败必须exit 1JSON 错误对象在 stdout、人类可读错误在 stderr成功保持exit 0REPL 模式吸收退出保持交互循环——这正是 Agent 判断命令成败、决定是否重试的根基字典返回型失败路径单独审计不抛异常而返回{success: False}或{error: ...}的路径不会被except Exception捕获必须显式检查并用 monkeypatch 测试钉死矩阵化编排地图测试用策略×尺寸×种子的组合矩阵 文件哨兵轮询把引擎内测试的结果以test_complete.json的方式异步回传规避了进程内同步等待的脆弱性对平台怪癖给出可操作处方stdinDEVNULL、显式 UTF-8避免能跑但不能移植的隐性债务。围绕 TEST.md 记载的清单配合 sbox_cli.py 的实现与 README.md 的命令手册读者既可以从零跑通整套测试也能深入理解每条契约背后的工程动机。【免费下载链接】CLI-AnythingCLI-Anything: Making ALL Software Agent-Native -- CLI-Hub: https://clianything.cc/项目地址: https://gitcode.com/GitHub_Trending/cl/CLI-Anything创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考