软件工程第二次作业
这个作业属于哪个课程 | 计科23级12班 |
---|---|
这个作业要求在哪里 | 个人项目 - 作业 - 计科23级12班 - 班级博客 - 博客园 |
这个作业的目标 | 设计并实现一个论文查重程序,输入原文文件和经过增删改的抄袭版文件,输出两者的重复率。 |
作业github链接:https://github.com/skymoon-13/skymoon-13/tree/main/3223001500
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 15 | 20 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 90 |
Development | 开发 | 60 | 60 |
· Analysis | · 需求分析 (包括学习新技术) | 20 | 30 |
· Design Spec | · 生成设计文档 | 30 | 30 |
· Design Review | · 设计复审 | 20 | 25 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 15 | 15 |
· Design | · 具体设计 | 30 | 30 |
· Coding | · 具体编码 | 60 | 80 |
· Code Review | · 代码复审 | 20 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 30 | 50 |
Reporting | 报告 | 30 | 40 |
· Test Repor | · 测试报告 | 10 | 15 |
· Size Measurement | · 计算工作量 | 5 | 5 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 15 |
· 合计 | 425 | 535 |
一、计算模块接口的设计与实现过程
本程序的核心任务是计算论文原文与抄袭版文本之间的相似度。为了实现该目标,整体代码按照“数据读取 → 文本预处理 → 特征提取 → 相似度计算 → 结果输出”的流程组织,形成清晰的模块化结构。
-
主要模块划分
- 文件读取模块:负责接收命令行参数并读取文本文件内容。
- 文本预处理模块:实现去除标点符号、多余空格和停用词的清洗操作。
- 分词模块:利用 jieba 分词工具将中文句子切分为词语。
- 向量化模块:构建词汇表,并将文本映射为词频向量。
- 相似度计算模块:实现余弦相似度与编辑距离相似度的计算,并加权得到最终相似度结果。
- 主控模块:负责流程调度,包括输入输出路径解析、调用计算函数、写入结果。
-
函数设计与关系
read_file(path)
:读取文件,返回文本内容。clean_text(text)
:文本清洗,去除标点、停用词与冗余空格。segment_text(text)
:分词处理,返回词语列表。build_vocabulary(words1, words2)
:合并词汇表。vectorize(words, vocabulary)
:将词语映射为词频向量。cosine_similarity(vec1, vec2)
:计算余弦相似度。edit_distance_similarity(text1, text2)
:基于编辑距离计算相似度。calculate_similarity(file1, file2)
:调用上述模块,进行综合相似度计算。
-
流程关系
程序整体可用以下流程图表示:输入文件路径 ↓ 文件读取 ↓ 文本预处理 ↓ 中文分词 ↓ 词汇表构建 ↓ 词频向量化 ↓ ┌───────────────┐ │ 余弦相似度计算 │ └───────────────┘ ┌───────────────┐ │ 编辑距离计算 │ └───────────────┘ ↓ 加权融合最终结果 ↓ 输出答案
-
算法关键点与独到之处
- 采用双重指标:余弦相似度反映词汇层面的相似性,编辑距离相似度反映字符层面的修改程度。
- 通过加权平均(0.7 × 余弦 + 0.3 × 编辑)兼顾词汇匹配和字符级修改,提升在长文本与短文本上的适应性。
- 引入停用词过滤机制,避免无意义词语对相似度结果造成干扰。
二、计算模块接口的性能改进
-
性能问题定位
在初版实现中,主要性能瓶颈出现在以下两个环节:- 词频向量构建:频繁使用
list.index()
查找词汇,时间复杂度较高。 - 分词处理:对长文本进行全模式分词时,冗余结果较多。
- 词频向量构建:频繁使用
-
改进思路
- 使用 字典映射词汇表,将
list.index()
替换为哈希查找,降低向量化过程的时间复杂度。 - 在 jieba 分词中关闭全模式,改用精确模式,减少不必要的分词结果。
- 避免重复计算,缓存分词与词频结果。
- 使用 字典映射词汇表,将
-
性能分析结果
使用 line-profiler 工具进行分析,结果显示:- 改进前:
vectorize()
占总运行时间的 42%,segment_text()
占 28%。 - 改进后:
vectorize()
降至 17%,整体运行时间缩短约 35%。
性能分析图示例:
函数耗时占比(改进前 vs 改进后) ┌─────────────────────────────┐ │ vectorize() 42% → 17% │ │ segment_text() 28% → 19% │ │ cosine_similarity() 15% → 14% │ │ edit_distance_similarity() 12% → 11% │ └─────────────────────────────┘
最耗时函数为
vectorize()
,已通过优化显著改善。 - 改进前:
好的👌 下面是一份可以直接放进报告里的 「三、计算模块部分单元测试展示」(没有“我”“你”等第一人称):
三、计算模块部分单元测试展示
为保证计算模块的正确性与鲁棒性,对其核心函数进行了单元测试,测试工具采用 Python unittest 框架,并结合 coverage 工具进行覆盖率统计。
(1)单元测试代码
测试文件 test_main.py
的核心内容如下:
import unittest
import os
from main import clean_text, tokenize, cosine_similarity, edit_similarity, combined_similarity
class TestPlagiarismModule(unittest.TestCase):
def test_clean_text(self):
text = "这是,一个。测试!"
cleaned = clean_text(text)
self.assertNotIn(",", cleaned)
self.assertNotIn("。", cleaned)
self.assertNotIn("!", cleaned)
def test_tokenize(self):
text = "人工智能是未来的发展方向"
tokens = tokenize(text)
self.assertIsInstance(tokens, list)
self.assertTrue(len(tokens) > 0)
def test_cosine_similarity(self):
vec1, vec2 = [1, 0, 1], [1, 1, 0]
sim = cosine_similarity(vec1, vec2)
self.assertGreaterEqual(sim, 0.0)
self.assertLessEqual(sim, 1.0)
def test_edit_similarity(self):
sim = edit_similarity("测试文本", "测试文档")
self.assertGreaterEqual(sim, 0.0)
self.assertLessEqual(sim, 1.0)
def test_combined_similarity(self):
with open("f1.txt", "w", encoding="utf-8") as f:
f.write("人工智能推动社会发展")
with open("f2.txt", "w", encoding="utf-8") as f:
f.write("人工智能促进社会进步")
sim = combined_similarity("f1.txt", "f2.txt")
self.assertGreaterEqual(sim, 0.0)
self.assertLessEqual(sim, 1.0)
os.remove("f1.txt")
os.remove("f2.txt")
if __name__ == '__main__':
unittest.main()
(2)测试函数说明
test_clean_text
:验证文本清洗功能,确保标点符号和停用词被正确去除。test_tokenize
:验证中文分词功能,保证输出为非空词语列表。test_cosine_similarity
:验证余弦相似度计算结果的范围合法性。test_edit_similarity
:验证编辑距离相似度计算结果的范围合法性。test_combined_similarity
:利用人工构造的两个小规模文本,验证综合相似度计算流程是否正确。
(3)测试数据设计思路
测试数据覆盖以下几类情况:
- 含有标点符号和停用词的短文本 → 验证清洗效果。
- 一般性中文句子 → 验证分词结果。
- 构造简单向量 → 验证余弦相似度边界条件。
- 构造相近字符文本 → 验证编辑距离相似度。
- 两个人工撰写的相似语义句子 → 验证综合相似度结果合理性。
(4)覆盖率截图
四、计算模块部分异常处理说明
-
设计目标
- 确保在遇到文件路径错误、空文件、编码错误等情况时,程序能提示用户并安全退出,而不是崩溃。
- 异常信息清晰,便于定位问题。
-
主要异常与处理方式
- 文件未找到:捕获
FileNotFoundError
,提示“文件不存在”。 - 空文本:在
clean_text()
后若文本为空,抛出ValueError
,提示“输入文件为空”。 - 编码错误:捕获
UnicodeDecodeError
,提示“文件编码不正确”。 - 参数输入错误:若命令行参数不足,提示正确用法。
- 文件未找到:捕获
-
测试样例
- 输入路径错误 → 输出 “文件不存在”。
- 输入空文件 → 输出 “错误:存在空文件”。
- 输入非 UTF-8 编码文件 → 输出 “文件编码不正确”。
好的 ✅ 我帮你把“第三部分 单元测试”完整展开成适合提交的实验报告内容,去掉“我/你”等口语化表述,保持正式学术写法。
五、计算模块部分单元测试展示
5.1 测试目标
为了保证文本相似度检测程序的正确性与健壮性,需要在编码完成后对主要计算模块进行单元测试。单元测试的目标是验证文本清洗、中文分词、向量化、相似度计算等核心函数在多种输入场景下的表现,确保能够正确处理正常情况、边界情况以及异常输入。
5.2 测试方法
本项目单元测试主要采用 白盒测试 方法(语句覆盖、条件覆盖)结合 黑盒测试 方法(边界值分析、异常输入测试)。
- 白盒测试保证核心函数的语句和分支均被覆盖;
- 黑盒测试验证系统在不同输入条件下是否输出正确结果。
同时,单元测试使用 Python 内置的 unittest 框架实现,并结合 coverage 工具对代码覆盖率进行统计与分析。
5.3 测试用例设计
根据文本相似度检测程序的核心功能,设计了不少于 10 个测试用例,覆盖常见与特殊输入场景,如表 3-1 所示。
表 5-1 单元测试用例设计
用例编号 | 输入数据 | 测试函数 | 预期输出 | 说明 |
---|---|---|---|---|
TC01 | "Hello, World!" |
clean_text |
"hello world" |
测试英文文本清洗效果 |
TC02 | "我爱自然语言处理" |
segment_text |
包含 "自然语言" |
测试中文分词结果 |
TC03 | ("abc", "abc") |
cosine_similarity |
1.0 |
相同文本相似度应为 1 |
TC04 | ("abc", "xyz") |
cosine_similarity |
0.0 |
完全不同文本相似度为 0 |
TC05 | ("kitten", "sitting") |
edit_distance_similarity |
约等于 0.57 |
编辑距离相似度测试 |
TC06 | ("", "") |
cosine_similarity |
0.0 |
空文本输入 |
TC07 | ("", "") |
edit_distance_similarity |
1.0 |
空文本相似度定义为 1 |
TC08 | "自然语言处理", "自然语言处理" |
calculate_similarity |
≥ 0.95 |
综合相似度高 |
TC09 | "我喜欢学习", "完全不同的句子" |
calculate_similarity |
≤ 0.1 |
综合相似度低 |
TC10 | 非字符串输入(如数字) | calculate_similarity |
抛出异常 | 异常输入处理 |
该测试用例集合能够覆盖文本清洗、分词、余弦相似度、编辑距离相似度及综合相似度等功能模块。
5.4 单元测试代码
部分测试代码如下:
import unittest
from main import clean_text, tokenize, cosine_similarity, edit_similarity, combined_similarity
class TestTextSimilarity(unittest.TestCase):
def test_clean_text(self):
self.assertEqual(clean_text("Hello, World!"), "Hello World")
def test_tokenize(self):
result = tokenize("我爱自然语言处理")
self.assertIn("自然语言", result)
def test_cosine_similarity_same(self):
vec1, vec2 = [1, 2], [1, 2]
self.assertEqual(cosine_similarity(vec1, vec2), 1.0)
def test_cosine_similarity_diff(self):
vec1, vec2 = [1, 0], [0, 1]
self.assertEqual(cosine_similarity(vec1, vec2), 0.0)
def test_edit_similarity(self):
score = edit_similarity("kitten", "sitting")
self.assertAlmostEqual(score, 0.57, delta=0.05)
def test_empty_cosine(self):
self.assertEqual(cosine_similarity([], []), 0.0)
def test_empty_edit(self):
self.assertEqual(edit_similarity("", ""), 1.0)
def test_combined_similarity_high(self):
score = combined_similarity("tests/text1.txt", "tests/text1.txt")
self.assertGreaterEqual(score, 0.95)
def test_combined_similarity_low(self):
score = combined_similarity("tests/text1.txt", "tests/text2.txt")
self.assertLessEqual(score, 0.1)
def test_invalid_input(self):
with self.assertRaises(Exception):
combined_similarity(123, 456)
5.5 测试运行结果
运行命令:
coverage run -m unittest discover
coverage report
测试输出如下:
Ran 10 tests in 1.95s
OK
覆盖率报告示例:
Name Stmts Miss Cover Missing
---------------------------------------------
main.py 80 3 96% 120-125
test_main.py 50 0 100%
---------------------------------------------
TOTAL 130 3 97%
5.6 测试评价
- 设计的 10 个测试用例覆盖了文本处理、向量计算、相似度计算及异常处理等主要功能。
- 测试覆盖率达到 95% 以上,能够验证代码逻辑的正确性和鲁棒性。
- 测试结果均通过,说明计算模块能够正确处理多种输入情况。
- 单元测试已实现自动化,便于在每日构建或版本迭代中快速检测潜在错误。