kohya_ss 回归测试体系详解:tests/ 目录的单元测试契约、Mock 策略与 pytest 运行指南
【免费下载链接】kohya_ss项目地址: https://gitcode.com/GitHub_Trending/ko/kohya_ss
导读
本文聚焦 kohya_ss 仓库根目录下tests/(复数)目录的定位、组织契约与验证方式:它是针对kohya_gui/包的 pytest/unittest 回归测试套件,专门覆盖无需 GPU、无需模型权重的纯 Python 逻辑(配置解析、能力探测、参数校验、命令行拼接等)。读完本文,你将掌握 kohya_ss 测试代码的组织原则(一模块一测试文件、importlib.reload触发 import 期副作用、Mock 外部环境依赖)、tests/conftest.py中共享 fixture 的用法,以及如何用pytest tests/一键运行整套回归验证。
一、tests/ 目录是什么:为 kohya_gui/ 量身定做的回归套件
在 kohya_ss 仓库结构中,tests/AGENTS.md 明确界定了tests/的核心使命:
pytest/unittest regression suite for
kohya_gui/behavior that's cheap to isolate (no GPU, no model weights).
即:它是一个回归测试套件(regression suite),针对的是kohya_gui/包的行为;测试目标必须"廉价可隔离"——不依赖 GPU、不加载模型权重。这意味着tests/里的测试聚焦于那些可以在纯 CPU 环境下断言正确的逻辑,例如:
- 配置解析与预设 JSON/TOML 的读写;
- 能力探测(如 TensorBoard 可见性、AVX 指令支持检测);
- 参数校验与命令行参数拼接(LoRA+ 比例、inpainting 开关等);
- 环境依赖判断(如
setup_common.installed()的包发现逻辑)。
这与仓库根 AGENTS.md 中描述的"Gradio 图形界面 + CLI 前端"定位一致:GUI 层把表单输入翻译成sd-scripts训练脚本的命令行调用,而这中间的"翻译"逻辑正是无需 GPU 即可单测验证的高价值代码。
所有权:镜像 kohya_gui/
tests/AGENTS.md的 Ownership 部分指出:tests/镜像(Mirrors)kohya_gui/——这里的目标是kohya_gui/包的纯 Python 逻辑,典型例子是 kohya_gui/class_tensorboard.py 的 AVX/TensorBoard 可用性检测。换句话说,tests/下的测试文件与被测的kohya_gui/模块一一对应,测试围绕 GUI 包而非sd-scripts子模块展开。
二、tests/ 与 test/:一字之差,职责截然不同
这是阅读 kohya_ss 测试代码时最容易混淆的一点。仓库中存在两个目录:
| 目录 | 复数tests/ | 单数test/ |
|---|---|---|
| 定位 | 自动化 pytest 回归套件(单元测试) | 手动端到端 scratch 空间 + 一个独立 unittest |
| 运行方式 | pytest tests/ | pytest test/test_allowed_paths.py |
| 内容性质 | 受控、可重复、Mock 外部依赖 | config/、log/、output/、img*等手工运行产物与临时 fixtures |
| 维护约定 | 新纯逻辑测试都应放这里 | 新启动器/CLI 参数转发测试放这里(见 test/AGENTS.md) |
具体分工在 test/AGENTS.md 中有更细的说明:test/下的test_allowed_paths.py是真正的回归测试(通过importlib.util.spec_from_file_location按文件路径加载根目录的kohya_gui.py启动器,而不是import kohya_gui),其余目录(config/、masked_loss/、logs/、img with spaces/等)均为手动跑训练时的 scratch 数据。"img with spaces/" 这个目录名特意保留空格,用于捕获生成的 CLI 命令中的路径引号 bug。
三、本地契约(Local Contracts):一模块一测试,reload 与 Mock 双管齐下
tests/AGENTS.md的 Local Contracts 部分给出了三条硬性约定:
3.1 每个被测模块对应一个test_<module_under_test>.py
即一模块一测试文件。例如被测模块kohya_gui/class_tensorboard.py对应 tests/test_tensorboard_visibility.py;kohya_gui/lora_gui.py对应 tests/test_lora_gui.py;setup/setup_common.py对应 tests/test_setup_common_installed.py。这种命名约定让测试与被测代码的对应关系一目了然,也便于 CI 中按文件定位失败原因。
3.2 使用importlib.reload重触发 import 期副作用
tests/AGENTS.md指出:当被测模块存在 import-time side effects,且需要在不同 Mock 条件下重新触发时,应在每个测试内对目标模块执行importlib.reload。这正是 tests/test_tensorboard_visibility.py 的做法:
import unittest from unittest.mock import patch import importlib # Since we are modifying an existing file, we need to reload it import kohya_gui.class_tensorboard importlib.reload(kohya_gui.class_tensorboard)为什么必须 reload?看 kohya_gui/class_tensorboard.py 的模块顶层代码:
visibility = bool(shutil.which("tensorboard") and check_avx_support())visibility是模块导入时一次性计算的模块级常量。若不 reload,Mock 对shutil.which/cpuinfo.get_cpu_info的替换不会反映到已导入模块的visibility上。测试中每次在@patch上下文内 reload 模块,才能让 Mock 结果生效。例如:
@patch('shutil.which', return_value='/usr/bin/tensorboard') @patch('cpuinfo.get_cpu_info', return_value={'flags': ['avx']}) def test_tensorboard_visibility_when_tensorboard_and_avx_are_present(self, mock_cpuinfo, mock_which): importlib.reload(kohya_gui.class_tensorboard) self.assertTrue(kohya_gui.class_tensorboard.visibility)对应的被测实现 kohya_gui/class_tensorboard.py:
def check_avx_support(): try: import cpuinfo info = cpuinfo.get_cpu_info() return "avx" in info.get("flags", []) except Exception: return False visibility = bool(shutil.which("tensorboard") and check_avx_support())可见visibility由两个条件共同决定:PATH 中能找到tensorboard可执行文件,且 CPU 支持 AVX 指令集。测试套件覆盖了"两者都满足 → 可见""缺 tensorboard → 不可见""缺 AVX → 不可见""cpuinfo 抛异常 → 安全返回 False"四条路径,其中check_avx_support的异常分支保证了探测失败时 GUI 不会崩溃。
3.3 Mock 外部/环境依赖,保证无 GPU 可运行
契约原文:Mock external/environment dependencies (shutil.which,cpuinfo.get_cpu_info, hardware/OS checks) — this suite must run without a GPU or real tensorboard/cpuinfo state.
即测试不得依赖宿主机真实的 TensorBoard 安装、CPU 特性或 GPU 状态,全部通过unittest.mock.patch注入假数据。这种策略让套件在任何开发机、CI runner 上都能稳定复现,也解释了为什么tests/可以被冠以"cheap to isolate"。
四、conftest.py:共享 fixture 与辅助函数
虽然tests/AGENTS.md没有逐条罗列,但 tests/conftest.py 是这套件得以"无 GPU、无模型、无 Gradio 会话"跑通的关键基础设施,值得展开:
resolve_repo_path(value):把 fixture 中的仓库相对路径(如./test/config/dataset.toml)解析为仓库根下的绝对路径,且不硬编码机器特定根目录,保证 WSL/Linux/Windows 行为一致。build_train_model_kwargs(train_model_fn, fixture_path, ...):从一个真实的预设 JSON fixture 出发,通过inspect.signature读取train_model()的完整形参签名,为 fixture 缺失的新字段按类型补默认值(布尔字段按BOOL_NAME_HINTS中的名称启发式补False,路径字段解析为仓库内路径,空字符串数字字段按numeric_fixups强转为 0),最终返回可直接调用train_model的完整 kwargs 字典。这样无需真实 Gradio 会话即可调用训练入口。mock_executor(gui_module):把<gui_module>.executor替换为 MagicMock,使train_model()的is_running()守卫通过。run_train_model_and_load_toml(...)/run_train_model_and_load_saved_json(...):在临时目录中以print_only=True(只写配置不启动训练)调用train_model,并把生成的 TOML/JSON 训练配置读回内存供断言。注意其中固定了get_executable_path的返回值为"accelerate",让测试不依赖宿主机是否安装了 accelerate——这正是契约中"Mock 环境依赖"的落地。
这些 helper 被 tests/test_lora_gui.py 等测试文件广泛 import(from conftest import build_train_model_kwargs, mock_executor, ...),是理解整个tests/套件工作方式的第一把钥匙。
五、实测维度示例:tests/ 到底在测什么
结合仓库中的测试文件,可以看到tests/覆盖的典型回归场景:
5.1 GUI 参数 → 训练配置的端到端回归(test_lora_gui.py)
tests/test_lora_gui.py 是体量最大、最能体现套件价值的文件之一,围绕多个真实 GitHub issue 展开:
- LoRA+ 比例参数:
append_loraplus_network_args把三个比例(loraplus_lr_ratio、loraplus_unet_lr_ratio、loraplus_text_encoder_lr_ratio)拼进network_args,0 或 None 的值不输出;同时验证生成的 TOML 中这些参数不会出现在顶层(test_loraplus_ratios_flow_through_network_args_not_top_level)。 - 废弃参数不再泄漏:
lowvram从 GUI 传入后不得出现在生成的配置中(GH issue #3520,sd-scripts v0.11.1 会静默忽略该参数)。 - inpainting 训练开关:
train_inpainting只能转发给train_network.py/sdxl_train_network.py,且必须与cache_latents/cache_latents_to_disk互斥(mask 是按步从原图随机生成的,不能缓存 latent);Flux 后端则必须丢弃该开关。 - Chroma 模型类型:
model_type=chroma时强制apply_t5_attn_mask、guidance_scale=0.0,并省略 CLIP-L;而默认 Flux 行为保持字节级兼容。 - FIELD_REGISTRY 顺序契约(GH #3543):
FIELD_REGISTRY的字段声明顺序必须与train_model/save_configuration/open_configuration的共享关键字参数顺序完全一致,否则按位置传参时所有后续值会静默错位——测试直接用inspect.signature比对两者。 - 按钮接线(dict-adapter wiring):通过
last_built_gui_entries拿到 Gradio 实际调用的绑定 callable,验证训练/保存/加载按钮按组件身份(而非位置)取参,并对比"走接线"与"直接调用"产生的配置完全一致。
5.2 TensorBoard 可见性探测(test_tensorboard_visibility.py)
上文已详述:通过 reload + patch 验证visibility的四种分支,确保 kohya_gui/class_tensorboard.py 的TensorboardManager在不可用环境下安全隐藏按钮(get_button_states依据visibility决定 Start/Stop 按钮的可见性),并通过TENSORBOARD_PORT/TENSORBOARD_HOST环境变量控制端口与监听地址(默认 6006 / 0.0.0.0)。
5.3 字符串型 lr_warmup 的容错(test_lr_warmup_resolve.py)
tests/test_lr_warmup_resolve.py 针对 GH issue #3455:Gradio 数字控件可能以字符串形式回传,lr_warmup若是字符串/None 会令lr_warmup_steps计算抛 TypeError。测试覆盖共享 helperresolve_lr_warmup_steps的纯逻辑(百分比换算、显式步数优先、空串/None 视为 0、非法字符串回退 0)以及 finetune 的集成路径,还断言BasicTraining.__init__的lr_warmup_value默认值必须是数值而非字符串。
5.4 setup_common.installed() 包发现(test_setup_common_installed.py)
tests/test_setup_common_installed.py 为 PR #3484 的importlib.metadata迁移做回归:覆盖包缺失返回 False、==/>=版本约束、extras 括号剥离(diffusers[torch]==0.32.2→ 查询diffusers)、大小写与下划线转连字符的兜底查找,并用真实可导入的pip做冒烟测试。注意它同样用importlib.util.spec_from_file_location按文件路径加载setup/setup_common.py,避免与kohya_gui包名冲突(与 test/AGENTS.md 的约定一致)。
六、新增测试怎么写:Work Guidance 的落地建议
tests/AGENTS.md的 Work Guidance 只有一条核心指引:
New pure-logic additions to
kohya_gui/(config parsing, capability detection, validation helpers) should get a unit test here rather than only being smoke-tested through the GUI.
即:凡是新增到kohya_gui/的纯逻辑(配置解析、能力探测、校验辅助函数),都应在此补一个单元测试,而不是只通过 GUI 手工冒烟。结合上面的源码分析,新增测试的推荐套路是:
- 创建
tests/test_<module>.py,按被测模块命名; - 若被测模块顶层有 import 期副作用(如计算常量),在测试内
importlib.reload该模块; - 用
unittest.mock.patchMock 掉shutil.which、cpuinfo、硬件/OS 探测等环境依赖; - 涉及
train_model配置生成的,复用conftest.py的build_train_model_kwargs+run_train_model_and_load_toml; - 保持与
test/(单数)的边界:启动器/CLI 参数转发测试放test/,纯逻辑测试放tests/。
七、如何运行:Verification 一节给出的唯一命令
tests/AGENTS.md的 Verification 只有一行:
pytest tests/配合仓库根 AGENTS.md 的指引,GUI 改动合入前的完整验证为:
pytest tests/ test/test_allowed_paths.py即同时跑tests/自动化套件和test/下的独立启动器 unittest。由于整个套件不依赖 GPU、模型权重和真实外部状态,在任何普通开发环境都能快速复现,这也是它被选为"改动 kohya_gui/ 纯逻辑后的第一道防线"的根本原因。
八、小结:tests/ 在 kohya_ss 工程实践中的位置
从仓库的 DOX 体系看,tests/AGENTS.md是根 AGENTS.md Child DOX Index 中的一员,与kohya_gui/、tools/、docs/、test/共同构成项目的文档-代码-测试治理框架。其核心工程思想可以总结为三点:
- 分层隔离:自动化单测(
tests/)与手动端到端(test/)严格区分,互不污染; - 零环境依赖:通过 reload + Mock 将能力探测类代码变成可重复验证的纯逻辑;
- 以回归为本:每个测试可追溯到具体 issue/PR(#3388、#3430、#3455、#3520、#3527、#3543 等),把 GUI 层最容易出现的"参数拼接漂移"风险固化进测试。
对想要为 kohya_ss 贡献代码或深入理解其 GUI 架构的开发者来说,tests/既是最好的入门教材(展示kohya_gui/各模块的核心契约),也是改动后必须通过的质量闸门。
【免费下载链接】kohya_ss项目地址: https://gitcode.com/GitHub_Trending/ko/kohya_ss
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考