首页
/ OBS Studio CLI Agent Harness 测试体系全解:153 项零安装用例如何保障 JSON 场景集合编辑的正确性

OBS Studio CLI Agent Harness 测试体系全解:153 项零安装用例如何保障 JSON 场景集合编辑的正确性

2026-09-08 11:13:06作者:凤尚柏Louis

导读

本文聚焦 obs-studio/agent-harness/cli_anything/obs_studio/tests/TEST.md 所记载的 OBS Studio CLI Agent Harness 测试体系——它面向一个“无需安装 OBS Studio 即可通过命令行编辑场景集合”的 Python 工具链,用 117 项单元测试 + 36 项端到端测试(共 153 项) 覆盖项目、场景、源、滤镜、音频、转场、输出与撤销会话八大模块。读完本文,你将掌握这套测试清单的全貌、各测试类的设计意图、其背后的 JSON 数据模型与参数校验实现(含全部合法值与取值范围),以及如何在本仓库中一键复现 153 passed 的测试结果。


一、测试文档定位:给“无头 OBS 编辑”立下验收标准

OBS Studio CLI Agent Harness 是一个有状态命令行接口,它把 OBS 的“场景集合(Scene Collection)”抽象为一份纯 JSON 文档,让 LLM / Agent 或脚本可以像操作文件一样编排直播与录制流程,而编辑全程不需要运行 OBS Studio。相关说明见 README.md,命令入口为 obs_studio_cli.py,核心业务逻辑全部集中在 core 目录下的 8 个模块。

在这种设计下,测试不再依赖 GUI 与真实采集设备——这正是 TEST.md 的立身之本。它既是测试资产的目录清单,也是这套“JSON 数据模型 + 纯函数操作”设计的回归契约:

  • 单元测试全部使用合成 / 内存数据,不要求安装 OBS Studio;
  • E2E 测试把多个核心操作编排成完整的直播搭建、录制配置、保存载回等真实工作流,同样在无 OBS 环境验证;
  • 测试结果可在 0.19s 内完成(Python 3.13.11 / pytest 9.0.2 环境),使每次迭代都能获得近乎即时的回归反馈。

二、测试资产总览:两组文件、16 个测试类、153 项用例

TEST.md 开篇给出测试清单总表,这也是理解整套测试的目录索引:

文件 测试类 用例数 关注点
test_core.py 8 117 project、scenes、sources、filters、audio、transitions、output、session 的单元测试
test_full_e2e.py 8 36 E2E 工作流:直播搭建、源操作、场景管理、滤镜链、输出配置、保存/加载、撤销/重做、边界用例
合计 16 153

两份测试文件均位于 tests 目录,与被测的 core 模块一一对应。需要补充说明的是:该目录中还实际存在 test_validate_geometry_nan.pytest_validate_range_nan.py 两个未计入 TEST.md 清单的专项文件;从文件名与 obs_utils.py 的实现来看,它们专门验证 _finite_number / _finite_int / validate_rangeNaNInfinity 等非有限数值的拒绝逻辑(例如 math.isfinite 检查),说明仓库的校验回归在 TEST.md 定稿后仍在持续加强。

运行方式与可复现基线

TEST.md 末尾给出一次完整运行记录,可直接作为“必须保持绿色”的基线:

============================= test session starts ==============================
platform linux -- Python 3.13.11, pytest-9.0.2, pluggy-1.5.0
rootdir: /root/cli-anything
plugins: langsmith-0.5.1, anyio-4.12.0
collected 153 items

test_core.py   117 passed
test_full_e2e.py   36 passed

============================= 153 passed in 0.19s ==============================

README.md 的说明,在 agent-harness 目录下运行 python3 -m pytest cli/tests/ -v 可复现全部用例;test_core.pytest_full_e2e.py 也支持单独运行,便于在开发某个模块时只跑对应回归。


三、单元测试分层解剖(test_core.py · 8 类 117 项)

单元测试的导入清单(test_core.py)直接揭示了被测 API 面:create_project/open_project/save_project、场景与源管理、滤镜与参数设置、音频混音、转场、输出配置、Session。下面按 TEST.md 的 8 个测试类逐一展开,并结合源码说明“为什么这样测”。

3.1 TestProject(16 项):项目生命周期与输入闸门

覆盖点包括:默认参数创建、自定义分辨率/FPS/编码器/项目名、非法参数拒绝、JSON 存取往返、打开不存在或非法文件、查询项目信息、默认项目自带头尾转场与推流/录制配置。

源码印证见 project.pycreate_project() 是全部校验的第一道闸门,默认值为 1920x1080 @ 30fpsx264 编码、视频码率 6000、音频码率 160;并强制以下约束(与测试的 pytest.raises 断言一一对应):

  • 分辨率必须为正整数(output_width < 1 or output_height < 1 → ValueError);
  • FPS 必须为正(fps < 1 → ValueError);
  • 视频码率 ≥ 100、音频码率 ≥ 32;
  • encoder 必须是 x264, x265, nvenc, qsv, amd, svt-av1 之一。

默认项目结构(project.py#L13-L54)预置 1 个场景(id=0)、Cut/Fade 两个转场、active_scene=0、空的 audio_sources、Twitch 推流占位与 MKV 录制占位——这正是“默认项目自带转场、推流与录制配置”这一断言的直接来源。打开文件时要求 JSON 必须含 versionscenes 字段(project.py#L92-L100),否则即便文件合法也会被判为非法项目文件。

3.2 TestScenes(11 项):场景数量的不变量

覆盖:增删场景、自动去重命名、唯一 ID、复制场景生成独立副本、切换活动场景、非法索引拒绝、列表查询,以及“删除当前活动场景后活动索引自动校正”的状态联动。

场景操作的核心不变式在源码中体现为至少保留 1 个场景索引校验remove_scene 会拒绝移除最后一个场景,索引访问统一走 obs_utils.py 的 get_item,空集合抛 ValueError、越界抛 IndexError。活动场景的调整逻辑保证项目始终处于“存在一个活动场景”的合法状态,这也使得后续所有场景级操作(增源、挂滤镜)都有稳定锚点。

3.3 TestSources(19 项):源是场景编辑的主力对象

覆盖:添加 video_capture全部 12 种源类型、非法类型拒绝、带位置/尺寸/settings 添加、负尺寸拒绝、自动唯一命名、删除/复制源、可见性与透明度等属性设置(含非法属性与越界透明度拒绝)、position/size/crop/rotation 变换、按索引查询与列表、源默认属性自动填充。

12 种源类型及其“类别 + 默认设置”注册在 sources.py#L17-L78SOURCE_TYPES 字典中:

类型 类别 默认设置要点
video_capture video device、1920x1080、30fps
display_capture video display=0、capture_cursor
window_capture video window、capture_cursor
image media file、unload_when_hidden
media media local_file、looping、restart_on_activate
browser web url、800x600、css
text text Sans Serif、36 号、白色
color utility 黑色 1920x1080
audio_input / audio_output audio device
group / scene utility items / scene_name

新源的默认结构(sources.py#L88-L105)固定含 position{x,y}size{1920,1080}crop{0,0,0,0}rotation=0opacity=1.0、空 filters 与类型默认 settings——这就是“默认属性已填充”断言的依据。对 size 的负值拒绝与 crop 的非负约束,实际由 obs_utils.py 的 validate_size / validate_crop 完成。

3.4 TestFilters(14 项):滤镜参数规格是“自带 schema”的注册表

覆盖:给源挂滤镜、带参数挂滤镜、非法滤镜类型/非法参数/越界参数拒绝、chroma_keynoise_suppress 专项、从源移除滤镜、设置滤镜参数、列出源上滤镜、列出全部可用滤镜及按类别过滤、所有滤镜类型都有参数定义

最后一类断言意义重大:FILTER_TYPES 注册表(filters.py#L8-L127)为每种滤镜声明的 params 自带 type / default / min / max / values,因此测试可以遍历注册表验证每项都定义了参数规格,保证“注册即可用”。参数校验函数 _validate_filter_paramsfilters.py#L139 起)会拒绝未知参数名并补齐默认值、执行范围检查,例如:

  • chroma_keysimilarity/smoothness/spill 取值范围 1–1000,key_color_type 限定 green/blue/magenta/custom
  • color_correction:gamma ∈ [-3,3]、contrast ∈ [-4,4]、hue_shift ∈ [-180,180] 等;
  • noise_suppress:method 限定 rnnoise/speex/nvafx,suppress_level ∈ [-60,0];
  • compressor:ratio ∈ [1,32]、threshold ∈ [-60,0]、attack ∈ [1,500] 等;
  • 还有 sharpennoise_gategainlimitercrop_padlutimage_maskscrollcolor_key

3.5 TestAudio(13 项):全局混音轨的完整状态机

覆盖:添加输入/输出型音频源、非法类型拒绝、音量设置与越界拒绝、静音/取消静音、监听模式设置、声道平衡与同步偏移、移除、列表与唯一命名。

约束来自 audio.pyaudio_type 仅允许 input/outputMONITOR_TYPESaudio.py#L8)限定监听模式为 none / monitor_only / monitor_and_output音量合法区间为 0.0–3.0(1.0 即 100%,支持超过 100% 的增益),声道平衡合法区间为 [-1.0, 1.0],同步偏移以毫秒计。这些范围正是测试中断言“越界即 ValueError”的规则来源。

3.6 TestTransitions(11 项):默认转场之外的类型与时长

覆盖:增删转场、自定义时长、非法类型与负时长拒绝、删除最后一个转场拒绝、设置时长、设置活动转场、列表与“所有转场类型均合法”。

七种转场类型及默认时长注册在 transitions.py#L8-L16cut(0ms)fade(300ms)swipe(500ms)slide(500ms)stinger(1000ms)fade_to_color(300ms)luma_wipe(500ms)。删除最后一个转场会被 remove_transition 拒绝——与场景的“至少一个”不变式同一思路。

3.7 TestOutput(14 项):推流、录制与编码的三层配置

覆盖:推流服务与配置、非法服务拒绝、设置推流密钥;录制路径/格式/质量、非法格式与质量拒绝;输出分辨率/FPS/码率设置、按预设置档配置、非法预设置档拒绝、非法输出宽度与编码器拒绝;输出信息查询、预设置档列表、合法服务与格式列表。

output.py 集中了三张白名单与 8 个内置编码预设置档:

  • VALID_SERVICES = twitch, youtube, facebook, custom
  • VALID_RECORDING_FORMATS = mkv, mp4, mov, flv, ts
  • VALID_RECORDING_QUALITIES = low, medium, high, lossless
  • ENCODING_PRESETSultrafast / fast / balanced / quality / high_quality(x264 系)、nvenc_fast / nvenc_quality(NVENC 系)与 recording_high(x264、20Mbps)。

set_output_settingsoutput.py#L67-L114)支持“先应用 preset,再覆盖单项”的叠加语义——这正是 E2E 中“预设置档后被单一参数覆盖、encoder 保留自预设置档”断言的实现基础。

3.8 TestSession(13 项):快照式撤销/重做的状态机

覆盖:会话创建、绑定项目、未绑定项目时取项目报错、撤销/重做往返、空撤销/重做、新快照清空 redo 栈、状态上报深度、保存会话到文件、历史列表、最大撤销数强制、撤销回退源添加与场景添加。

实现集中在 session.pySession 类:MAX_UNDO = 50session.py#L39),每次变更前由 snapshot() 深度拷贝当前项目入 undo 栈并清空 redo 栈undo() 把当前状态压入 redo 后回弹 undo 栈顶;save_session 通过 _locked_save_jsonsession.py#L10-L33)用 fcntl 独占锁实现原子写盘。测试验证的“新快照清空 redo”“undo 上限 50”“保存无路径抛 ValueError”等,都能在此逐行找到对应分支。


四、E2E 工作流解剖(test_full_e2e.py · 8 类 36 项)

单元测试验证“每个原子操作正确”,E2E 测试则把原子操作编排成 Agent / 脚本真实会执行的整段流程,验证跨模块协作后的最终状态。全部 9 组场景的编排风格可参看 test_full_e2e.py#L30-L60(创建项目→加 3 个场景→往不同场景放源→配置 Twitch 推流),逐类要点如下:

E2E 测试类 用例数 编排要点
TestStreamSetupWorkflow 3 完整直播搭建(4 场景 + webcam/display/overlay/BRB 图 + Twitch + balanced 预置);绿幕机位(video_capture + chroma_key/color_correction);音频调音台(麦克风输入 + 桌面输出 + 音量 + 监听模式)
TestSourceManipulation 4 四层叠加场景(游戏捕获/边框/webcam/text)自定义位置;位置-缩放-裁剪-旋转全变换;复制文本源并独立控制可见性;显隐切换
TestSceneWorkflow 4 多场景各自承载不同源并核对数量;切换活动场景;带源复制场景并验证副本独立;删场景不影响其他场景
TestFilterChains 4 音频五连滤镜链(noise_suppress→noise_gate→compressor→gain→limiter);视频三连滤镜链(chroma_key→color_correction→sharpen);链中改参;从链中间删滤镜且保持顺序
TestTransitionWorkflow 2 在默认 Cut/Fade 之外追加 stinger 与 slide;修改转场时长
TestOutputConfiguration 2 完整输出配置(YouTube 推流 + MP4 高质量录制 + 1080p60/8Mbps);先 preset 后覆盖单项
TestSaveLoadRoundtrip 2 全量往返(场景/源/滤镜/音频/转场/推流/录制);保存/载回后源 transform 保持一致
TestSessionUndoRedo 5 撤销源添加、场景添加、双滤镜两步撤销、音量变更撤销、3 次改名 + 3 撤 + 2 重做的复合序列
TestEdgeCases 10 空项目信息、对不存在场景取源(IndexError)、对不存在源挂滤镜(ValueError)、空场景删源、变换不存在源、负裁剪值、全部源/滤镜类型均可添加、chroma key 拒绝非法颜色类型、会话无路径保存、20 场景 + 21 源的大集合

值得强调的是 TestEdgeCases:它把单元测试里的“非法入参拒绝”升级为跨层行为契约——例如“源挂在不存在场景上抛 IndexError、滤镜挂在不存在源上抛 ValueError”,直接约束了 scenes → sources → filters 的索引链错误类型划分(场景层越界抛 IndexError、资源层空/缺失抛 ValueError)。这类断言对 Agent 非常重要:LLM 在生成 CLI 操作序列时可以根据异常类型精确判断“是索引写错了还是资源不存在”。


五、为什么“不装 OBS 也能测”:源码结构的可测性设计

153 项用例在 0.19 秒内跑完,根本原因是被测对象与 OBS 本体彻底解耦:

  1. 数据即 JSON:项目、场景、源、滤镜、转场、推流/录制配置全部是纯 Python dict,任何操作都以 dict 为入参与返回值,天然可用内存构造、用 json.dumps 快照对比;
  2. core 层为纯函数模块core 下每个模块都是“(project, 参数...) → 变更后 dict”的无副作用函数,不触碰硬件采集、不依赖 X11/GPU,可测性与可移植性由此而来;
  3. 校验集中在 utilsgenerate_id / unique_name / get_item / validate_range / validate_position / validate_size / validate_crop / _finite_number / _finite_int 等通用函数(obs_utils.py)统一处理边界、越界、非有限数值(NaN/Infinity),使各模块的错误语义保持一致,测试可批量覆盖;
  4. CLI 只是薄壳obs_studio_cli.py 通过 Click 将 core 函数映射成命令,会话状态由全局 Session 持有,并支持 --json 结构化输出与 REPL 交互模式,因此测试直接打 core 层即可获得完整逻辑覆盖,无需启动进程。

从源码结构可以推断,这套设计把“编辑场景集合”压缩成了可复现、可版本控制、可被语言模型直接操作的纯数据处理问题——这正是其测试可以不依赖任何 OBS 运行时的根本原因,也解释了为什么文档反复强调 “No OBS Studio installation required”。


六、从测试反推 API 契约:一份可复用的参数边界速查

综合 TEST.md 与各 core 模块源码,可以得到一张面向开发与 Agent 调用的契约速查表:

领域 参数 合法范围 / 白名单
项目 resolution / fps / 码率 宽高 ≥ 1、fps ≥ 1、video ≥ 100kbps、audio ≥ 32kbps
项目 encoder x264 / x265 / nvenc / qsv / amd / svt-av1
场景 数量 至少保留 1 个;索引越界抛 IndexError
类型 12 种(见 SOURCE_TYPES
size / crop 宽高 ≥ 1;crop 各边 ≥ 0
opacity [0.0, 1.0]
滤镜 参数 FILTER_TYPES 注册表的 type/min/max/values 校验,未知参数拒绝
音频 volume / balance volume ∈ [0.0, 3.0];balance ∈ [-1.0, 1.0]
音频 监听模式 none / monitor_only / monitor_and_output
转场 类型 / 时长 7 种类型;时长 ≥ 0ms;至少保留 1 个
推流 服务 twitch / youtube / facebook / custom
录制 格式 / 质量 mkv / mp4 / mov / flv / tslow / medium / high / lossless
编码 预设置档 8 个内置档(ultrafastrecording_high),可单项覆盖
会话 undo 深度 MAX_UNDO = 50,新快照清空 redo

测试用例对“错误类型”的划分也值得直接复用:文件层FileNotFoundError(如 test_core.py 的 open 测试),场景索引越界IndexError资源空集合 / 参数越界 / 非法白名单值统一用 ValueError。CLI 层的 handle_error 包装器 会把这些异常转换成 {"error": ..., "type": ...} 结构化输出或非零退出码,供上层 Agent 判断。


七、回归运行建议与结语

在开发任一 core 模块(例如新增源类型、扩展滤镜参数、调整编码预置)后,建议按以下节奏回归:

# 在 obs-studio/agent-harness 目录下
python3 -m pytest cli/tests/ -v          # 全量 153 项
python3 -m pytest cli/tests/test_core.py -v          # 仅单元测试(117 项)
python3 -m pytest cli/tests/test_full_e2e.py -v      # 仅 E2E(36 项)

每次新增注册表条目(SOURCE_TYPES / FILTER_TYPES / TRANSITION_TYPES / ENCODING_PRESETS)时,记得补一条“全部类型可添加 / 均有参数定义”的断言——这正是 TEST.md 中最具“契约自检”价值的测试思路。此外,已存在的 test_validate_geometry_nan.pytest_validate_range_nan.py 提示:对任何接受数值入参的新 API,都应把 NaN/Infinity 拒绝纳入测试面,与 obs_utils.py 的 _finite_number 保持同一防线。

总而言之,这套测试体系的价值不止于“153 passed”:它以纯 JSON 数据模型 + 纯函数 core + 集中式校验为前提,把一份庞大的 OBS 场景集合编辑能力压缩为毫秒级、零依赖、Agent 可调用的可靠契约。对任何想在 CI 中验证“直播/录制配置生成逻辑”或让 LLM 安全编排 OBS 工作流的开发者而言,TEST.md 与其背后的 test_core / test_full_e2e 就是最直接的范式参考。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391