按"测试规则"重写 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。
98 lines
3.4 KiB
Python
98 lines
3.4 KiB
Python
"""nodes/echo.py 的模块级测试(数据 → 测试过程 → 验证结果)。
|
|
|
|
被测模块:`nodes/echo.py`(示例回显节点),用于验证节点协议与注册表链路,
|
|
可独立调用。测试只调用真实 `invoke`,自行准备输入数据并检查产物。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from nodes.echo import invoke
|
|
from wov_sdk.models import InvokeRequest
|
|
|
|
|
|
def _request(tmp_path: Path, inputs: dict) -> InvokeRequest:
|
|
"""构造一个真实 InvokeRequest;输出目录指向本用例的临时目录。"""
|
|
return InvokeRequest(
|
|
run_id="run-test",
|
|
node_instance_id="echo-1",
|
|
params={},
|
|
inputs=inputs,
|
|
output_dir=str(tmp_path / "out"),
|
|
)
|
|
|
|
|
|
def test_invoke_echoes_text_input(tmp_path: Path) -> None:
|
|
"""传入 text 时直接回显该文本,并把文本写入产物文件。"""
|
|
# 数据:请求直接携带文本。
|
|
request = _request(tmp_path, {"text": "你好,字幕"})
|
|
|
|
# 测试过程:调用真实节点处理器。
|
|
response = invoke(request)
|
|
|
|
# 验证结果:状态为 completed,输出文本一致,产物文件内容一致。
|
|
assert response.status == "completed"
|
|
assert response.outputs["text"] == "你好,字幕"
|
|
artifact = Path(response.outputs["file_uri"])
|
|
assert artifact.read_text(encoding="utf-8") == "你好,字幕"
|
|
|
|
|
|
def test_invoke_reads_absolute_file_uri(tmp_path: Path) -> None:
|
|
"""未给 text 时读取 file_uri 指向的绝对路径文件内容。"""
|
|
# 数据:临时目录下的真实输入文件。
|
|
source = tmp_path / "input.txt"
|
|
source.write_text("来自文件的文本", encoding="utf-8")
|
|
|
|
# 测试过程
|
|
response = invoke(_request(tmp_path, {"file_uri": str(source)}))
|
|
|
|
# 验证结果
|
|
assert response.outputs["text"] == "来自文件的文本"
|
|
|
|
|
|
def test_invoke_resolves_relative_file_uri_from_workspace(tmp_path: Path) -> None:
|
|
"""相对 file_uri 以仓库根为基准解析(节点不启动独立进程,无自己的工作目录)。"""
|
|
# 数据:相对路径指向仓库根下的真实文件。
|
|
workspace = Path(__file__).resolve().parents[3]
|
|
relative = "pyproject.toml"
|
|
assert (workspace / relative).is_file()
|
|
|
|
# 测试过程
|
|
response = invoke(_request(tmp_path, {"file_uri": relative}))
|
|
|
|
# 验证结果:读到的内容与仓库根下该文件一致。
|
|
assert response.outputs["text"] == (workspace / relative).read_text(encoding="utf-8")
|
|
|
|
|
|
def test_invoke_defaults_to_fixed_text(tmp_path: Path) -> None:
|
|
"""既无 text 也无 file_uri 时返回固定文本,保证链路总有可演示输出。"""
|
|
# 数据:空输入。
|
|
request = _request(tmp_path, {})
|
|
|
|
# 测试过程
|
|
response = invoke(request)
|
|
|
|
# 验证结果
|
|
assert response.outputs["text"] == "echo"
|
|
|
|
|
|
def test_invoke_creates_output_directory(tmp_path: Path) -> None:
|
|
"""输出目录不存在时由节点创建,产物始终落在请求给定的 output_dir。"""
|
|
# 数据:输出目录尚未创建(_request 只给路径)。
|
|
output_dir = tmp_path / "not-yet"
|
|
|
|
# 测试过程
|
|
request = InvokeRequest(
|
|
run_id="run-test",
|
|
node_instance_id="echo-1",
|
|
params={},
|
|
inputs={"text": "x"},
|
|
output_dir=str(output_dir),
|
|
)
|
|
response = invoke(request)
|
|
|
|
# 验证结果:目录被创建且产物在目录内。
|
|
assert output_dir.is_dir()
|
|
assert Path(response.outputs["file_uri"]).parent == output_dir
|