76a0fdd00c
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Failing after 0s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 0s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 0s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
316 lines
10 KiB
Python
Executable File
316 lines
10 KiB
Python
Executable File
"""
|
||
text_splitter 长文本分段工具单元测试
|
||
|
||
覆盖:
|
||
- 空文本 / 短文本
|
||
- 句子边界分段(。!?;\n . ! ? ;)
|
||
- 超长句子硬切
|
||
- 过短段落合并
|
||
- max_chars 参数
|
||
- 中英文混合
|
||
"""
|
||
|
||
import pytest
|
||
|
||
from packages.application.tts_job.text_splitter import split_text
|
||
|
||
# ============================================================
|
||
# 基础场景
|
||
# ============================================================
|
||
|
||
|
||
class TestBasicCases:
|
||
"""基础场景"""
|
||
|
||
def test_empty_text_returns_empty_list(self):
|
||
assert split_text("") == []
|
||
|
||
def test_whitespace_only_returns_empty(self):
|
||
assert split_text(" \n\n ") == []
|
||
|
||
def test_short_text_single_segment(self):
|
||
text = "这是一段短文本。"
|
||
result = split_text(text, max_chars=500)
|
||
assert result == [text]
|
||
|
||
def test_exactly_max_chars_single_segment(self):
|
||
text = "a" * 500
|
||
result = split_text(text, max_chars=500)
|
||
assert len(result) == 1
|
||
assert len(result[0]) == 500
|
||
|
||
def test_text_stripped(self):
|
||
text = " 你好世界。 "
|
||
result = split_text(text, max_chars=500)
|
||
assert result == ["你好世界。"]
|
||
|
||
|
||
# ============================================================
|
||
# 句子边界分段
|
||
# ============================================================
|
||
|
||
|
||
class TestSentenceBoundarySplitting:
|
||
"""句子边界分段"""
|
||
|
||
def test_split_by_chinese_period(self):
|
||
text = "第一句。第二句。第三句。"
|
||
# 三句都很短,应该合并成一段
|
||
result = split_text(text, max_chars=500)
|
||
assert len(result) == 1
|
||
|
||
def test_split_by_chinese_period_long_text(self):
|
||
"""多段长句子,按句号分段"""
|
||
sentence1 = "我是第一句" + "啊" * 100 + "。"
|
||
sentence2 = "我是第二句" + "哦" * 100 + "。"
|
||
sentence3 = "我是第三句" + "嗯" * 100 + "。"
|
||
text = sentence1 + sentence2 + sentence3
|
||
|
||
result = split_text(text, max_chars=150)
|
||
# 每句106字符,超过150的阈值?不,106<150
|
||
# 但累计到一定程度会切
|
||
assert len(result) >= 2
|
||
# 每段都不超过 max_chars
|
||
for seg in result:
|
||
assert len(seg) <= 150
|
||
|
||
def test_split_by_question_mark(self):
|
||
text = "你是谁?你从哪里来?你要到哪里去?"
|
||
result = split_text(text, max_chars=500)
|
||
# 三句都很短,合并成一段
|
||
assert len(result) == 1
|
||
|
||
def test_split_by_exclamation_mark(self):
|
||
text = "太棒了!太厉害了!太牛了!"
|
||
result = split_text(text, max_chars=500)
|
||
assert len(result) == 1
|
||
|
||
def test_split_by_newline(self):
|
||
text = "第一段\n第二段\n第三段"
|
||
result = split_text(text, max_chars=500)
|
||
assert len(result) == 1
|
||
|
||
def test_split_by_semicolon(self):
|
||
text = "第一部分;第二部分;第三部分。"
|
||
result = split_text(text, max_chars=500)
|
||
assert len(result) == 1
|
||
|
||
def test_mixed_punctuation(self):
|
||
"""混合标点符号的句子边界"""
|
||
parts = []
|
||
for i in range(20):
|
||
parts.append(f"第{i}句的内容" + "字" * 30 + "。")
|
||
text = "".join(parts)
|
||
|
||
result = split_text(text, max_chars=200)
|
||
# 每句约35字符,200字符大约能放5-6句
|
||
assert len(result) >= 2
|
||
for seg in result:
|
||
assert len(seg) <= 200
|
||
|
||
def test_english_period_splitting(self):
|
||
text = "Hello. How are you. I am fine."
|
||
result = split_text(text, max_chars=500)
|
||
assert len(result) == 1
|
||
|
||
def test_english_question(self):
|
||
text = "What? Why? How?"
|
||
result = split_text(text, max_chars=500)
|
||
assert len(result) == 1
|
||
|
||
|
||
# ============================================================
|
||
# 超长硬切
|
||
# ============================================================
|
||
|
||
|
||
class TestLongSentenceHardCut:
|
||
"""超长句子硬切"""
|
||
|
||
def test_single_very_long_sentence_hard_cut(self):
|
||
"""单个超长句子,没有标点,硬切"""
|
||
text = "字" * 1000
|
||
result = split_text(text, max_chars=500)
|
||
assert len(result) == 2
|
||
assert len(result[0]) == 500
|
||
assert len(result[1]) == 500
|
||
|
||
def test_three_times_max_chars(self):
|
||
text = "字" * 1500
|
||
result = split_text(text, max_chars=500)
|
||
assert len(result) == 3
|
||
for seg in result:
|
||
assert len(seg) == 500
|
||
|
||
def test_not_exact_multiple(self):
|
||
text = "字" * 1250
|
||
result = split_text(text, max_chars=500)
|
||
assert len(result) == 3
|
||
assert len(result[0]) == 500
|
||
assert len(result[1]) == 500
|
||
assert len(result[2]) == 250
|
||
|
||
def test_all_segments_within_limit(self):
|
||
"""所有段都不超过 max_chars"""
|
||
import random
|
||
|
||
random.seed(42)
|
||
# 生成随机长度的文本
|
||
text = "".join(random.choices("字字字字。!?;\n", k=5000))
|
||
for max_chars in [100, 200, 500]:
|
||
result = split_text(text, max_chars=max_chars)
|
||
for i, seg in enumerate(result):
|
||
assert len(seg) <= max_chars, f"Segment {i} length {len(seg)} > {max_chars}"
|
||
|
||
|
||
# ============================================================
|
||
# 过短段落合并
|
||
# ============================================================
|
||
|
||
|
||
class TestShortSegmentMerging:
|
||
"""过短段落合并"""
|
||
|
||
def test_short_final_segment_merged(self):
|
||
"""最后一段过短,应该合并到前一段"""
|
||
# 构造:前一段接近上限,后一段很短
|
||
long_part = "字" * 480 + "。"
|
||
short_part = "好的。"
|
||
text = long_part + short_part
|
||
|
||
result = split_text(text, max_chars=500)
|
||
# 两段加起来 481+3=484 < 500,可能合并
|
||
# 但要看具体实现...
|
||
# 至少验证所有段不超长
|
||
for seg in result:
|
||
assert len(seg) <= 500
|
||
|
||
def test_multiple_short_segments(self):
|
||
"""多个短段落应该合并"""
|
||
sentences = ["你好。", "我好。", "大家好。", "今天天气不错。", "适合出去玩。"]
|
||
text = "".join(sentences)
|
||
result = split_text(text, max_chars=500)
|
||
# 5个短句子,应该合并成一段
|
||
assert len(result) == 1
|
||
|
||
|
||
# ============================================================
|
||
# max_chars 参数
|
||
# ============================================================
|
||
|
||
|
||
class TestMaxCharsParameter:
|
||
"""max_chars 参数"""
|
||
|
||
def test_small_max_chars(self):
|
||
text = "一二三四五六七八九十一二三四五六七八九十。"
|
||
result = split_text(text, max_chars=10)
|
||
# 应该被切成多段
|
||
assert len(result) >= 2
|
||
for seg in result:
|
||
assert len(seg) <= 10
|
||
|
||
def test_custom_max_chars_200(self):
|
||
text = "测试文本" * 100 # 400字符
|
||
result = split_text(text, max_chars=200)
|
||
assert len(result) == 2
|
||
assert len(result[0]) == 200
|
||
assert len(result[1]) == 200
|
||
|
||
def test_very_small_max_chars(self):
|
||
text = "abcdefghij"
|
||
result = split_text(text, max_chars=3)
|
||
assert len(result) >= 3
|
||
for seg in result:
|
||
assert len(seg) <= 3
|
||
|
||
|
||
# ============================================================
|
||
# 中英文混合
|
||
# ============================================================
|
||
|
||
|
||
class TestMixedContent:
|
||
"""中英文混合内容"""
|
||
|
||
def test_chinese_english_mixed(self):
|
||
text = "今天天气很好,Today is sunny. 我们去公园玩吧!Let's go to the park."
|
||
result = split_text(text, max_chars=500)
|
||
assert len(result) == 1
|
||
assert result[0] == text.strip()
|
||
|
||
def test_mixed_long_text(self):
|
||
parts = []
|
||
for i in range(50):
|
||
parts.append(f"第{i}段中文内容" + "字" * 20 + ". English part " + "word " * 10 + "。")
|
||
text = "".join(parts)
|
||
|
||
result = split_text(text, max_chars=300)
|
||
assert len(result) >= 2
|
||
for seg in result:
|
||
assert len(seg) <= 300
|
||
|
||
|
||
# ============================================================
|
||
# 输出完整性
|
||
# ============================================================
|
||
|
||
|
||
class TestOutputIntegrity:
|
||
"""输出完整性验证"""
|
||
|
||
def test_combined_length_equals_original(self):
|
||
"""所有段拼接起来(去掉空段)应该等于原文长度"""
|
||
text = "这是第一段。这是第二段。这是第三段。这是第四段。这是第五段。" * 20
|
||
result = split_text(text, max_chars=100)
|
||
combined = "".join(result)
|
||
# 由于 strip 可能去掉一些空格,原文也 strip 比较
|
||
assert len(combined) == len(text.strip())
|
||
|
||
def test_order_preserved(self):
|
||
"""分段后再拼接,文本顺序不变"""
|
||
text = "第一。第二。第三。第四。第五。" * 10
|
||
result = split_text(text, max_chars=50)
|
||
combined = "".join(result)
|
||
assert combined == text.strip()
|
||
|
||
def test_no_empty_strings_in_result(self):
|
||
"""结果中没有空字符串"""
|
||
text = "句子一。句子二。句子三。"
|
||
result = split_text(text, max_chars=10)
|
||
for seg in result:
|
||
assert seg != ""
|
||
assert len(seg) > 0
|
||
|
||
|
||
# ============================================================
|
||
# 边界情况
|
||
# ============================================================
|
||
|
||
|
||
class TestEdgeCases:
|
||
"""边界情况"""
|
||
|
||
def test_single_character(self):
|
||
assert split_text("一", max_chars=500) == ["一"]
|
||
|
||
def test_only_punctuation(self):
|
||
text = "。。。。。"
|
||
result = split_text(text, max_chars=500)
|
||
# 都是标点,也算文本
|
||
assert len(result) == 1
|
||
|
||
def test_only_newlines(self):
|
||
text = "\n\n\n"
|
||
result = split_text(text, max_chars=500)
|
||
assert result == []
|
||
|
||
def test_long_text_many_sentences(self):
|
||
"""大量句子的长文本"""
|
||
sentences = [f"第{i}句的完整内容。" for i in range(100)]
|
||
text = "".join(sentences)
|
||
result = split_text(text, max_chars=200)
|
||
assert len(result) >= 5
|
||
for seg in result:
|
||
assert len(seg) <= 200
|