软工作业:用python实现论文查重

github项目地址

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/CSGrade22-34
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13229
这个作业的目标 python实现论文查重并进行单元测试

一、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 25
Estimate 估计这个任务需要多少时间 30 35
Development 开发 1000 1400
Analysis 需求分析 (包括学习新技术) 120 150
Design Spec 生成设计文档 30 30
Design Review 设计复审 30 35
Coding Standard 代码规范 (为目前的开发制定合适的规范) 15 20
Design 具体设计 60 60
Coding 具体编码 70 120
Code Review 代码复审 20 30
Test 测试(自我测试,修改代码,提交修改) 60 60
Reporting 报告 20 60
Test Repor 测试报告 20 20
Size Measurement 计算工作量 10 15
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 10 10
合计 1500 1800

二、运行环境

IDE:PyCharm 2024.1.4

三、主函数设计

流程图

一、程序分析

程序实现了以下主要功能:

  1. 文件读取功能 (read_file):
  • 从指定的文件路径读取文件内容,如果文件不存在,则抛出 FileNotFoundError 并退出程序。
  • 使用了 sys.exit(1) 来确保程序在找不到文件时以状态码 1 退出,表示异常状态。
  • 该函数的设计非常简单,读取文件内容时使用了上下文管理(with open),确保即使在异常情况下文件也会被正确关闭。
  • 错误处理部分使用了 try...except 捕获 FileNotFoundError,这很好地避免了文件不存在时的崩溃。
  1. 文本预处理功能 (preprocess_text):
  • 对文本进行预处理,步骤包括:
  • 使用正则表达式去除所有非单词字符和空白字符之外的内容(即去掉标点符号等)。
  • 使用 jieba.lcut 进行中文分词,之后将分词结果用空格连接为一个字符串。jieba 是一种常用的中文分词库,适合处理中文文本。
  • 使用正则表达式去除文本中的标点符号,这是非常有效的清理方式。
  • jieba.lcut 可以将中文文本进行分词,分词后再通过 ' '.join 拼接成适合向量化处理的格式。这一部分对中文文本处理非常重要,因为中文是没有空格分隔的,直接向量化处理中文可能会产生不正确的结果。*
  1. 相似度计算功能 (calculate_similarity):
  • 使用 TfidfVectorizer 对输入的两个文本进行词频-逆文档频率(TF-IDF)向量化处理。
  • 生成的TF-IDF矩阵用于计算两个文本之间的 余弦相似度,这是一种常用的文本相似度度量方法。计算的余弦相似度结果在0到1之间,0表示完全不同,1表示完全相同。
  1. 相似度写入功能 (write_output):
  • 将计算出的相似度结果写入指定的文件。结果会被格式化为保留两位小数的形式,并在文件中显示。
  • 该函数直接将相似度结果格式化并写入指定的输出文件,功能简单明了。
  • 使用 with open 确保文件操作安全,不会因异常情况导致文件未关闭或数据未写入。

二、设计优点

该程序通过合理的模块化设计、可靠的错误处理、适当的中文处理工具、稳健的文本相似度计算方法,以及安全的文件操作,展现了以下优点:

  • 简洁易用:每个功能都十分明确,接口简单,用户可以轻松使用。
  • 高效准确:利用 TF-IDF 和余弦相似度计算文本相似度,具有较高的准确性。
  • 可维护性高:由于功能模块独立、职责单一,代码非常易于修改和维护。

三、主要函数设计代码

1.文件读取功能 (read_file):

def read_file(file_path):
    """
    读取指定路径的文件内容,并返回去掉空白符的字符串。
    :param file_path: 文件路径
    :return: 去掉首尾空白符后的字符串
    """
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            return file.read().strip()  # 读取文件内容并去掉首尾空白符
    except FileNotFoundError:
        # 文件未找到时,打印错误信息并退出程序
        print(f"File {file_path} not found.")
        sys.exit(1)

2.文本预处理功能 (preprocess_text):

def preprocess_text(text):
    """
    预处理输入文本,去除标点符号并使用结巴分词对中文进行分词。
    :param text: 输入的原始文本
    :return: 经过标点去除和分词后的文本
    """
    # 使用正则表达式去除文本中的标点符号
    text = re.sub(r'[^\w\s]', '', text)
    # 使用结巴分词对文本进行分词,并通过空格连接分词结果
    return ' '.join(jieba.lcut(text))

3.相似度计算功能 (calculate_similarity):

def calculate_similarity(text1, text2):
    """
    计算两个文本之间的TF-IDF余弦相似度。
    :param text1: 文本1
    :param text2: 文本2
    :return: 计算得到的余弦相似度值
    """
    # 初始化TF-IDF向量化器
    vectorizer = TfidfVectorizer()
    # 将两个文本转换为TF-IDF矩阵
    tfidf_matrix = vectorizer.fit_transform([text1, text2])
    # 计算两个文本的余弦相似度,返回第一个文本与第二个文本的相似度值
    return cosine_similarity(tfidf_matrix[0], tfidf_matrix[1])[0][0]

4.相似度写入功能 (write_output):

def write_output(output_path, similarity):
    """
    将相似度结果写入指定的输出文件。
    :param output_path: 输出文件路径
    :param similarity: 计算得到的相似度值
    """
    # 以写入模式打开文件,并将相似度写入文件,保留两位小数
    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(f"{similarity:.2f}\n")

四、性能分析

通过cprofile可以对整个程序的执行进行性能分析,找到耗时较多的部分,然后进一步优化。
main函数运行

cProfile.run("main(orig_path, plagiarized_path, output_path)", filename="performance_analysis_result")

命令行运行

snakeviz.exe -p 8080 .\performance_analysis_result

得到性能分析图

可见在主函数中preprocess_text运行时间最长,故可以考虑对函数preprocess_text进行优化。

五、单元测试

关于测试点的思维导图

测试代码如下:
1.文件操作测试
测试点1: 文件读取成功
测试点2: 文件不存在错误处理
测试点3: 文件输出 - 正确写入相似度
测试点4: 文件输出 - 文件不可写错误处理

# 文件操作测试
# 测试点1: 文件读取成功
def test_read_file_success():
    mock_data = "这是一个测试文件"
    with patch("builtins.open", mock_open(read_data=mock_data)) as mock_file:
        result = read_file("test.txt")
        mock_file.assert_called_once_with("test.txt", "r", encoding="utf-8")
        assert result == mock_data.strip()


# 测试点2: 文件不存在错误处理
def test_read_file_not_found():
    with patch("builtins.open", side_effect=FileNotFoundError):
        with pytest.raises(SystemExit):
            read_file("non_existent.txt")


# 测试点3: 文件输出 - 正确写入相似度
def test_write_output_success():
    similarity = 0.85
    with patch("builtins.open", mock_open()) as mock_file:
        write_output("output.txt", similarity)
        mock_file.assert_called_once_with("output.txt", "w", encoding="utf-8")
        mock_file().write.assert_called_once_with("0.85\n")


# 测试点4: 文件输出 - 文件不可写错误处理
def test_write_output_file_permission_error():
    with patch("builtins.open", side_effect=PermissionError):
        with pytest.raises(PermissionError):
            write_output("output.txt", 0.85)

2.文本预处理测试
测试点5: 文本预处理 - 去除标点符号
测试点6: 文本预处理 - 分词功能
测试点7: 文本预处理 - 空白文本处理
测试点8: 长文本处理

# 文本预处理测试
# 测试点5: 文本预处理 - 去除标点符号
def test_preprocess_text_remove_punctuation():
    text = "这是一个测试,包含标点符号!"
    expected_output = "这是 一个 测试 包含 标点符号"
    result = preprocess_text(text)
    assert result == expected_output


# 测试点6: 文本预处理 - 分词功能
def test_preprocess_text_segmentation():
    text = "这是一个测试"
    expected_output = "这是 一个 测试"
    result = preprocess_text(text)
    assert result == expected_output


# 测试点7: 文本预处理 - 空白文本处理
def test_preprocess_empty_text():
    text = ""
    result = preprocess_text(text)
    assert result == ""


# 测试点8: 长文本处理
def test_long_text_processing():
    long_text = "这是一个非常长的测试文本。" * 10000  # 模拟非常长的文本
    result = preprocess_text(long_text)
    assert isinstance(result, str)
    assert len(result) > 0  # 检查处理后的文本不为空

3.相似度计算测试
测试点9: 计算相似度 - 完全相同文本
测试点10: 计算相似度 - 完全不同文本
测试点11: 计算相似度 - 部分相似文本
测试点12: TF-IDF 向量化处理

# 相似度测试
# 测试点9: 计算相似度 - 完全相同文本
def test_calculate_similarity_identical_text():
    text1 = "测试文本"
    text2 = "测试文本"
    similarity = calculate_similarity(text1, text2)
    assert similarity == 1.0


# 测试点10: 计算相似度 - 完全不同文本
def test_calculate_similarity_different_text():
    text1 = "这是测试A"
    text2 = "这是测试B"
    similarity = calculate_similarity(text1, text2)
    assert similarity == 0.0


# 测试点11: 计算相似度 - 部分相似文本
def test_calculate_similarity_partial_similarity():
    text1 = "这是一个测试"
    text2 = "这是一个不同的测试"
    orig_text1 = preprocess_text(text1)
    orig_text2 = preprocess_text(text2)
    similarity = calculate_similarity(orig_text1, orig_text2)
    assert 0 < similarity < 1.0


# 测试点12: TF-IDF 向量化处理
def test_tfidf_vectorization():
    text = "我喜欢学习"
    vectorizer = TfidfVectorizer()
    tfidf_matrix = vectorizer.fit_transform([text])
    assert tfidf_matrix.shape == (1, len(vectorizer.get_feature_names_out()))

测试覆盖率

posted @ 2024-09-14 09:35  hechangzhou0309  阅读(16)  评论(0编辑  收藏  举报