test: 按模块重写测试代码,删除旧平铺结构
按"测试规则"重写 tests/:一个模块一个目录、用例按数据→过程→验证三段书写、 不保留全局 conftest.py、测试过程只调用真实生产代码。 结构(73 个文件、30 个模块目录、477 用例): - tests/nodes/ 15 个模块目录(srt/whisper/ass/ffmpeg/frame_extract/vlm/ subtitle_ocr/llm/llm_filter/subtitle_cleanup/subtitle_correction/ proper_nouns/adaptive_pool/vad_profiler/echo); - tests/app/ 11 个模块目录(db/scheduler/batch/maintenance/registry/seed/ storage/config/logging/main/routers 三组 API); - tests/sdk/test_models、tests/web/test_crop、tests/shared(公共设施)。 测试数据随模块目录入库(tests/**/data/),删除根级 testdata/;.gitignore 的 data/ 改为 /data/,否则会连带忽略 tests/**/data/ 导致测试数据无法入库。 顺带发现并修复三个真实缺陷: - nodes/srt.py:相邻条目缺少空行时把下一条时间轴吞进正文(静默错位), 改为正文行遇时间戳行即报错; - src/wov_app/scheduler.py:_file_size 只捕获 OSError,含 \x00 的产物 URI 抛 ValueError 导致任务误判失败,改为同时捕获; - nodes/subtitle_correction.py:生产代码依赖测试包解析 SRT, 改用生产模块 nodes/srt.py。 真实模型/服务集成测试按外部状态跳过:新增 tests/shared/gpu_memory.py (运行时探测显存、CUDA OOM 转跳过)与 tests/shared/llm_service.py (无 Key / 余额 / 限流转跳过)。全量 477 passed。
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
"""src/wov_app/config.py 的模块级测试(数据 → 测试过程 → 验证结果)。
|
||||
|
||||
被测模块:`src/wov_app/config.py`(环境变量读取与路径推导),可独立调用。
|
||||
由于该模块在**导入时**锁定路径常量,隔离用例在子进程中运行(真实导入路径
|
||||
+ 干净环境),其余用例校验默认值与类型。
|
||||
|
||||
注意:config 的路径常量在进程内已固化,因此"环境变量覆盖"必须用子进程验证,
|
||||
否则测的是缓存值而非真实行为(这也是规则要求"可独立运行、不依赖外部配置"
|
||||
的具体体现)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from wov_app import config
|
||||
|
||||
# 仓库根目录(config.WORKSPACE_ROOT 应该指向它)。
|
||||
WORKSPACE = Path(__file__).resolve().parents[3]
|
||||
|
||||
|
||||
# 与路径相关的环境变量:子进程验证前必须清除,否则会继承外层测试隔离值。
|
||||
_PATH_ENV_KEYS = (
|
||||
"WOV_DATA_DIR", "WOV_DB_PATH", "WOV_STORAGE_DIR",
|
||||
"WOV_SCHEDULER_INTERVAL_SECONDS", "WOV_BATCH_INTERVAL_SECONDS",
|
||||
"WOV_CLEANUP_INTERVAL_SECONDS", "WOV_CLEANUP_GRACE_SECONDS",
|
||||
"WOV_BATCH_ENABLED", "WOV_CLEANUP_ENABLED",
|
||||
)
|
||||
|
||||
|
||||
def _run_in_subprocess(code: str, env: dict[str, str] | None = None) -> dict:
|
||||
"""在干净子进程中导入 config 并返回指定变量(真实进程隔离)。
|
||||
|
||||
先清除全部相关环境变量,再用 env 注入本次用例的值,保证测到的是
|
||||
config 的真实解析逻辑而不是外层测试运行环境。
|
||||
"""
|
||||
script = (
|
||||
"import json\n"
|
||||
"from wov_app import config\n"
|
||||
f"{code}\n"
|
||||
"print(json.dumps(result))\n"
|
||||
)
|
||||
clean_env = {k: v for k, v in __import__("os").environ.items() if k not in _PATH_ENV_KEYS}
|
||||
clean_env.update(env or {})
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-c", script],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=str(WORKSPACE),
|
||||
env=clean_env,
|
||||
)
|
||||
assert result.returncode == 0, result.stderr
|
||||
return json.loads(result.stdout.strip().splitlines()[-1])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 默认值(当前进程内已导入的常量)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_workspace_root_points_to_repo_root() -> None:
|
||||
"""WORKSPACE_ROOT 指向仓库根(用于推导其余路径)。"""
|
||||
# 数据:模块常量。
|
||||
# 测试过程与验证结果
|
||||
assert config.WORKSPACE_ROOT == WORKSPACE
|
||||
assert (config.WORKSPACE_ROOT / "pyproject.toml").is_file()
|
||||
|
||||
|
||||
def test_default_paths_are_under_data_dir() -> None:
|
||||
"""无环境变量时默认路径为 <仓库根>/data 下的推导值(子进程验证真实默认)。"""
|
||||
# 数据:清空全部相关环境变量。
|
||||
# 测试过程
|
||||
result = _run_in_subprocess(
|
||||
"result = {'root': str(config.WORKSPACE_ROOT), 'data': str(config.DATA_DIR), "
|
||||
"'db': str(config.DB_PATH), 'storage': str(config.STORAGE_DIR), "
|
||||
"'batch': config.BATCH_ENABLED, 'cleanup': config.CLEANUP_ENABLED}"
|
||||
)
|
||||
|
||||
# 验证结果:默认 data 目录在仓库根下,db/storage 由它推导,开关默认开。
|
||||
assert result["root"] == str(WORKSPACE)
|
||||
assert result["data"] == str(WORKSPACE / "data")
|
||||
assert result["db"] == str(WORKSPACE / "data" / "wov.db")
|
||||
assert result["storage"] == str(WORKSPACE / "data" / "storage")
|
||||
assert result["batch"] is True
|
||||
assert result["cleanup"] is True
|
||||
|
||||
|
||||
def test_numeric_settings_are_floats() -> None:
|
||||
"""数值型配置被解析为 float(避免字符串参与算术)。"""
|
||||
# 数据:模块常量。
|
||||
# 测试过程与验证结果
|
||||
for value in (
|
||||
config.SCHEDULER_INTERVAL_SECONDS,
|
||||
config.BATCH_INTERVAL_SECONDS,
|
||||
config.CLEANUP_INTERVAL_SECONDS,
|
||||
config.CLEANUP_GRACE_SECONDS,
|
||||
):
|
||||
assert isinstance(value, float)
|
||||
assert value > 0
|
||||
|
||||
|
||||
def test_boolean_settings_are_bool() -> None:
|
||||
"""布尔型开关被解析为 bool,默认全部开启(1)。"""
|
||||
# 数据:模块常量(测试运行环境由隔离层设为 0,故仅校验类型)。
|
||||
# 测试过程与验证结果
|
||||
for value in (config.BATCH_ENABLED, config.CLEANUP_ENABLED):
|
||||
assert isinstance(value, bool)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 环境变量覆盖(子进程真实导入)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_data_dir_env_override_changes_all_derived_paths(tmp_path: Path) -> None:
|
||||
"""WOV_DATA_DIR 覆盖后,DB_PATH 与 STORAGE_DIR 随之推导(路径联动)。"""
|
||||
# 数据:自定义数据目录。
|
||||
custom = tmp_path / "custom-data"
|
||||
|
||||
# 测试过程
|
||||
result = _run_in_subprocess(
|
||||
"result = {'data': str(config.DATA_DIR), 'db': str(config.DB_PATH), "
|
||||
"'storage': str(config.STORAGE_DIR)}",
|
||||
env={"WOV_DATA_DIR": str(custom)},
|
||||
)
|
||||
|
||||
# 验证结果
|
||||
assert result["data"] == str(custom)
|
||||
assert result["db"] == str(custom / "wov.db")
|
||||
assert result["storage"] == str(custom / "storage")
|
||||
|
||||
|
||||
def test_explicit_db_and_storage_env_take_precedence(tmp_path: Path) -> None:
|
||||
"""WOV_DB_PATH / WOV_STORAGE_DIR 可独立覆盖(不跟随 DATA_DIR)。"""
|
||||
# 数据:分别指定的数据库与存储路径。
|
||||
db_path = tmp_path / "x" / "custom.db"
|
||||
storage = tmp_path / "y" / "store"
|
||||
|
||||
# 测试过程
|
||||
result = _run_in_subprocess(
|
||||
"result = {'db': str(config.DB_PATH), 'storage': str(config.STORAGE_DIR)}",
|
||||
env={"WOV_DB_PATH": str(db_path), "WOV_STORAGE_DIR": str(storage)},
|
||||
)
|
||||
|
||||
# 验证结果
|
||||
assert result["db"] == str(db_path)
|
||||
assert result["storage"] == str(storage)
|
||||
|
||||
|
||||
def test_boolean_env_parsing() -> None:
|
||||
"""开关型环境变量:'1' 为 True,'0' 为 False。"""
|
||||
# 数据:批量与清理开关分别置 1 与 0。
|
||||
# 测试过程
|
||||
result = _run_in_subprocess(
|
||||
"result = {'batch': config.BATCH_ENABLED, 'cleanup': config.CLEANUP_ENABLED}",
|
||||
env={"WOV_BATCH_ENABLED": "1", "WOV_CLEANUP_ENABLED": "0"},
|
||||
)
|
||||
|
||||
# 验证结果
|
||||
assert result["batch"] is True
|
||||
assert result["cleanup"] is False
|
||||
|
||||
|
||||
def test_interval_env_parsing() -> None:
|
||||
"""轮询间隔环境变量被解析为对应浮点值。"""
|
||||
# 数据:指定的调度与批量间隔。
|
||||
# 测试过程
|
||||
result = _run_in_subprocess(
|
||||
"result = {'sched': config.SCHEDULER_INTERVAL_SECONDS, "
|
||||
"'batch': config.BATCH_INTERVAL_SECONDS}",
|
||||
env={"WOV_SCHEDULER_INTERVAL_SECONDS": "2.5", "WOV_BATCH_INTERVAL_SECONDS": "0.25"},
|
||||
)
|
||||
|
||||
# 验证结果
|
||||
assert result["sched"] == 2.5
|
||||
assert result["batch"] == 0.25
|
||||
|
||||
|
||||
def test_paths_are_pathlib_objects(tmp_path: Path) -> None:
|
||||
"""路径配置是 pathlib.Path(跨平台,不写死 Windows 盘符)。"""
|
||||
# 数据:自定义数据目录。
|
||||
# 测试过程
|
||||
result = _run_in_subprocess(
|
||||
"result = {'is_path': isinstance(config.DATA_DIR, __import__('pathlib').Path)}",
|
||||
env={"WOV_DATA_DIR": str(tmp_path / "d")},
|
||||
)
|
||||
|
||||
# 验证结果
|
||||
assert result["is_path"] is True
|
||||
Reference in New Issue
Block a user