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,179 @@
|
||||
"""nodes/proper_nouns.py 的模块级测试(数据 → 测试过程 → 验证结果)。
|
||||
|
||||
被测模块:`nodes/proper_nouns.py`(专名/拟声/成人隐语提示词规则),供
|
||||
llm-translate 注入系统提示词,纯函数可独立调用。
|
||||
|
||||
覆盖:未命中不注入、三类规则(专名/拟声/隐语)各自命中、批量文本输入、
|
||||
规则文本包含可执行的翻译指令。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from nodes.proper_nouns import (
|
||||
ADULT_EUPHEMISMS,
|
||||
ONOMATOPOEIA,
|
||||
PROPER_NOUNS,
|
||||
build_proper_noun_rule,
|
||||
)
|
||||
|
||||
|
||||
def test_returns_none_when_nothing_matches() -> None:
|
||||
"""普通文本未命中任何规则表时不注入(避免干扰正常翻译)。"""
|
||||
# 数据:不含规则表词条的普通句子。
|
||||
text = "今天天气真好。\n我们一起去散步吧。"
|
||||
|
||||
# 测试过程
|
||||
rule = build_proper_noun_rule(text)
|
||||
|
||||
# 验证结果
|
||||
assert rule is None
|
||||
|
||||
|
||||
def test_empty_input_returns_none() -> None:
|
||||
"""空文本与空列表都不注入规则。"""
|
||||
# 数据:空字符串与空列表。
|
||||
# 测试过程与验证结果
|
||||
assert build_proper_noun_rule("") is None
|
||||
assert build_proper_noun_rule([]) is None
|
||||
|
||||
|
||||
def test_injects_proper_noun_rule_with_advice() -> None:
|
||||
"""命中专名表时注入该词的处置建议(禁止硬译)。"""
|
||||
# 数据:真实字幕片段,含角色专名 ジンゴ。
|
||||
text = "クモ穴にパンパンになって ジンゴがここで味わいませんか"
|
||||
|
||||
# 测试过程
|
||||
rule = build_proper_noun_rule(text)
|
||||
|
||||
# 验证结果:规则非空,含原词与处置说明。
|
||||
assert rule is not None
|
||||
assert "ジンゴ" in rule
|
||||
assert "芒果" in rule # 说明文本中包含"禁止译作芒果"的约束
|
||||
|
||||
|
||||
def test_injects_person_name_rule() -> None:
|
||||
"""人名类专名命中时给出音译建议(不保留日文写法)。"""
|
||||
# 数据:含人名 カンタくん。
|
||||
text = "カンタくん、そこにいたの"
|
||||
|
||||
# 测试过程
|
||||
rule = build_proper_noun_rule(text)
|
||||
|
||||
# 验证结果
|
||||
assert rule is not None
|
||||
assert "カンタくん" in rule
|
||||
assert "音译" in rule
|
||||
|
||||
|
||||
def test_injects_onomatopoeia_rule() -> None:
|
||||
"""拟声/拟态词命中时给出按语境翻译的建议。"""
|
||||
# 数据:含拟态词 ビクビク。
|
||||
text = "体がビクビク震えてる"
|
||||
|
||||
# 测试过程
|
||||
rule = build_proper_noun_rule(text)
|
||||
|
||||
# 验证结果
|
||||
assert rule is not None
|
||||
assert "ビクビク" in rule
|
||||
|
||||
|
||||
def test_injects_adult_euphemism_with_actual_meaning() -> None:
|
||||
"""成人语境隐语命中时注入"实际含义 + 应译词 + 禁止字面直译"。"""
|
||||
# 数据:含隐语 マンゴー(实际指女性性器官)。
|
||||
text = "そろそろマンゴーが濡れてきました"
|
||||
|
||||
# 测试过程
|
||||
rule = build_proper_noun_rule(text)
|
||||
|
||||
# 验证结果:规则提示这是隐语并禁止字面直译。
|
||||
assert rule is not None
|
||||
assert "マンゴー" in rule
|
||||
assert "隐语" in rule
|
||||
assert "切勿按字面直译" in rule
|
||||
|
||||
|
||||
def test_accepts_batch_line_list() -> None:
|
||||
"""接受原文行列表(按批翻译时传入多行),跨行命中同样注入。"""
|
||||
# 数据:命中词分散在不同行。
|
||||
lines = ["普通的一句话", "相手がバナナをしゃぶってくれて"]
|
||||
|
||||
# 测试过程
|
||||
rule = build_proper_noun_rule(lines)
|
||||
|
||||
# 验证结果
|
||||
assert rule is not None
|
||||
assert "バナナ" in rule
|
||||
|
||||
|
||||
def test_rule_text_covers_all_injected_tokens() -> None:
|
||||
"""多词同时命中时每条都出现在规则文本中(不丢词)。"""
|
||||
# 数据:同时含专名、拟声、隐语各一个。
|
||||
text = "ジンゴとビクビクとマンゴーの話"
|
||||
|
||||
# 测试过程
|
||||
rule = build_proper_noun_rule(text)
|
||||
|
||||
# 验证结果:三个词都在规则里。
|
||||
assert rule is not None
|
||||
for token in ("ジンゴ", "ビクビク", "マンゴー"):
|
||||
assert token in rule
|
||||
|
||||
|
||||
def test_each_rule_table_entry_is_self_consistent() -> None:
|
||||
"""规则表结构自检:每张表的每条记录字段完整,避免维护时漏字段。
|
||||
|
||||
这不重复业务逻辑,而是保证数据资产(表)本身可用——新增词条时若写错
|
||||
结构,此处会立刻失败。
|
||||
"""
|
||||
# 数据:三张规则表。
|
||||
tables = (PROPER_NOUNS, ONOMATOPOEIA, ADULT_EUPHEMISMS)
|
||||
|
||||
# 测试过程与验证结果
|
||||
for table in tables:
|
||||
assert table, "规则表不应为空"
|
||||
for token, payload in table.items():
|
||||
assert token.strip(), "词条不能为空白"
|
||||
assert all(str(part).strip() for part in payload), f"{token} 的说明字段不完整"
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_rule_matches_real_transcript_data() -> None:
|
||||
"""真实数据校准:真实日文 transcript 中含专名的片段应命中并注入规则。
|
||||
|
||||
数据来源:`data/` 下真实任务的 transcript.srt(gitignored,缺失即跳过)。
|
||||
"""
|
||||
# 数据:仓库 data/ 下真实产物的 transcript 文件。
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from tests.shared.srt_entries import parse_srt_entries
|
||||
|
||||
workspace = Path(__file__).resolve().parents[3]
|
||||
# 真实产物里的字幕(ASR 转录或过滤后字幕都可能含专名,两者都扫)。
|
||||
candidates = sorted(
|
||||
p for pattern in ("steps/asr/transcript.srt", "steps/filter/*.srt", "steps/ocr/subtitle.srt")
|
||||
for p in (workspace / "data" / "storage").glob(f"runs/*/{pattern}")
|
||||
if p.is_file()
|
||||
)
|
||||
if not candidates:
|
||||
pytest.skip("缺少真实字幕后端产物(data/ 未保留运行产物),跳过")
|
||||
|
||||
# 测试过程:在真实字幕里找**实际出现的**专名(不写死某个词,
|
||||
# 数据内容随影片变化),用它的上下文构造规则。
|
||||
for path in candidates:
|
||||
entries = parse_srt_entries(path.read_text(encoding="utf-8"))
|
||||
for token in PROPER_NOUNS:
|
||||
batch = [e["text"] for e in entries if token in e["text"]]
|
||||
if not batch:
|
||||
continue
|
||||
rule = build_proper_noun_rule(batch)
|
||||
|
||||
# 验证结果:命中专名即注入规则,且规则可安全序列化进提示词。
|
||||
assert rule is not None, f"真实数据含专名 {token},应注入规则"
|
||||
assert token in rule, f"规则应包含命中的专名 {token}"
|
||||
assert json.dumps(rule, ensure_ascii=False)
|
||||
return
|
||||
pytest.skip("真实字幕中未出现规则表内的专名,跳过")
|
||||
Reference in New Issue
Block a user