软件工程第二次作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13468
这个作业的目标 <实现一个论文查重程序,规范软件开发流程,训练个人项目开发能力>
Github链接 https://gitee.com/suzette11/3223004731

1. psp表格

Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
计划 30 25
估计这个任务需要多少时间 30 40
开发 250 300
需求分析 (包括学习新技术) 60 75
生成设计文档 45 50
设计复审 30 20
代码规范 (为目前的开发制定合适的规范) 10 20
具体设计 60 50
具体编码 100 120
代码复审 40 45
测试(自我测试,修改代码,提交修改) 45 60
报告 90 90
测试报告 25 60
计算工作量 45 30
事后总结, 并提出过程改进计划 45 60

2. 计算模块接口的设计与实现过程
论文查重系统
├── 应用层 (main.py)
├── 业务逻辑层 (paper_checker.py)
└── 数据处理层 (text_processor.py)

流程函数的协作关系
用户输入路径 →
read_text() 读取文本 →
preprocess() 处理文本 →
calculate_similarity() 计算相似度 →
write_result() 输出结果

屏幕截图 2025-09-22 223326

单元测试
代码
import unittest
import os
from main import FileHandler, TextPreprocessor, SimilarityCalculator, PlagiarismChecker

class TestFileHandler(unittest.TestCase):
"""测试文件处理类"""

def setUp(self):
    """创建测试临时文件"""
    self.test_file = "test_temp.txt"
    with open(self.test_file, 'w', encoding='utf-8') as f:
        f.write("测试文本内容")

def tearDown(self):
    """清理测试文件"""
    if os.path.exists(self.test_file):
        os.remove(self.test_file)

def test_read_text_success(self):
    """测试成功读取文件"""
    content = FileHandler.read_text(self.test_file)
    self.assertEqual(content, "测试文本内容")

def test_read_text_not_found(self):
    """测试读取不存在的文件"""
    with self.assertRaises(Exception) as context:
        FileHandler.read_text("nonexistent.txt")
    self.assertIn("文件不存在", str(context.exception))

def test_write_result(self):
    """测试结果写入功能"""
    output_file = "test_output.txt"
    FileHandler.write_result(output_file, 0.85)
    
    # 验证文件内容
    with open(output_file, 'r', encoding='utf-8') as f:
        content = f.read()
    self.assertEqual(content, "0.85")
    
    os.remove(output_file)

class TestTextPreprocessor(unittest.TestCase):
"""测试文本预处理类"""

def setUp(self):
    self.preprocessor = TextPreprocessor()

def test_clean_text(self):
    """测试文本清洗功能"""
    raw_text = "今天是星期天,天气晴!今天晚上我要去看电影。"
    cleaned = self.preprocessor.clean_text(raw_text)
    self.assertEqual(cleaned, "今天是星期天 天气晴 今天晚上我要去看电影")

def test_segment(self):
    """测试中文分词功能"""
    text = "今天天气晴朗"
    words = self.preprocessor.segment(text)
    self.assertEqual(list(words), ["今天", "天气", "晴朗"])

def test_filter_stopwords(self):
    """测试停用词过滤"""
    words = ["今天", "的", "天气", "很", "好"]
    filtered = self.preprocessor.filter_stopwords(words)
    self.assertEqual(filtered, ["今天", "天气", "好"])  # "的"和"很"被过滤

def test_preprocess_empty_text(self):
    """测试空文本处理"""
    result = self.preprocessor.preprocess("   ")
    self.assertEqual(result, "")

class TestSimilarityCalculator(unittest.TestCase):
"""测试相似度计算类"""

def setUp(self):
    self.calculator = SimilarityCalculator()
    self.preprocessor = TextPreprocessor()

def test_identical_text(self):
    """测试完全相同的文本"""
    text1 = "今天是星期天,天气晴,今天晚上我要去看电影。"
    text2 = "今天是星期天,天气晴,今天晚上我要去看电影。"
    
    processed1 = self.preprocessor.preprocess(text1)
    processed2 = self.preprocessor.preprocess(text2)
    
    similarity = self.calculator.calculate_similarity(processed1, processed2)
    self.assertAlmostEqual(similarity, 1.0, places=2)

def test_similar_text(self):
    """测试相似文本(样例数据)"""
    text1 = "今天是星期天,天气晴,今天晚上我要去看电影。"
    text2 = "今天是周天,天气晴朗,我晚上要去看电影。"
    
    processed1 = self.preprocessor.preprocess(text1)
    processed2 = self.preprocessor.preprocess(text2)
    
    similarity = self.calculator.calculate_similarity(processed1, processed2)
    self.assertGreater(similarity, 0.7)  # 预期相似度较高

def test_different_text(self):
    """测试完全不同的文本"""
    text1 = "今天是星期天,天气晴,今天晚上我要去看电影。"
    text2 = "明天是星期一,会下雨,我打算在家看书。"
    
    processed1 = self.preprocessor.preprocess(text1)
    processed2 = self.preprocessor.preprocess(text2)
    
    similarity = self.calculator.calculate_similarity(processed1, processed2)
    self.assertLess(similarity, 0.3)  # 预期相似度较低

def test_empty_texts(self):
    """测试两个空文本"""
    similarity = self.calculator.calculate_similarity("", "")
    self.assertEqual(similarity, 1.0)

def test_one_empty_text(self):
    """测试一个空文本和一个非空文本"""
    similarity = self.calculator.calculate_similarity("测试文本", "")
    self.assertEqual(similarity, 0.0)

class TestPlagiarismChecker(unittest.TestCase):
"""测试整体查重流程"""

def setUp(self):
    self.checker = PlagiarismChecker()
    # 创建测试文件
    self.orig_file = "orig_test.txt"
    self.copy_file = "copy_test.txt"
    with open(self.orig_file, 'w', encoding='utf-8') as f:
        f.write("今天是星期天,天气晴,今天晚上我要去看电影。")
    with open(self.copy_file, 'w', encoding='utf-8') as f:
        f.write("今天是周天,天气晴朗,我晚上要去看电影。")

def tearDown(self):
    """清理测试文件"""
    for file in [self.orig_file, self.copy_file]:
        if os.path.exists(file):
            os.remove(file)

def test_full_check流程(self):
    """测试完整查重流程"""
    similarity = self.checker.check(self.orig_file, self.copy_file)
    self.assertIsInstance(similarity, float)
    self.assertTrue(0 <= similarity <= 1)

if name == 'main':
# 生成测试报告
unittest.main(verbosity=2)

单元测试说明

  1. 测试覆盖的函数与模块
    FileHandler 类:测试read_text()和write_result(),验证文件读写功能及异常处理
    TextPreprocessor 类:测试clean_text()、segment()、filter_stopwords()等预处理函数
    SimilarityCalculator 类:测试核心的calculate_similarity()函数
    PlagiarismChecker 类:测试整体查重流程的完整性
  2. 测试数据构造思路
    正常场景:使用题目提供的样例文本(原文和抄袭版),验证相似度计算合理性
    边界场景:
    空文本(两个空文本、一个空文本)
    完全相同的文本(预期相似度 1.0)
    完全不同的文本(预期相似度接近 0)
    异常场景:
    读取不存在的文件
    包含特殊符号和标点的文本
    包含大量停用词的文本

3. 测试目的
验证核心算法的正确性(如相似文本的相似度高于 0.7)
确保边界情况得到正确处理(如空文本)
验证异常处理机制有效(如文件不存在时抛出合理异常)
保障代码重构和优化过程中功能的稳定性

计算模块异常处理说明
一、异常类型与设计目标
| 异常场景 | 处理目标 | 核心作用 |

文件不存在 捕获文件路径错误,避免程序崩溃 明确提示用户检查输入文件路径
权限不足 处理文件读写权限问题 引导用户更换有足够权限的文件路径
空文本输入 识别无效输入(如空白文件) 避免因空文本导致的计算逻辑异常
文本处理失败 捕获分词或清洗过程中的异常 确保预处理阶段错误不影响整体流程
命令行参数错误 校验输入参数数量和格式 规范用户输入,提供正确用法提示
二、异常处理实现与测试样例
  1. 文件不存在异常
    设计目标:当用户提供的原文或抄袭版论文路径不存在时,捕获FileNotFoundError并返回友好提示,避免程序直接崩溃。
    核心代码:
    with open(file_path, 'r', encoding='utf-8') as file:
    return file.read()
    except FileNotFoundError:
    raise Exception(f"错误:文件 '{file_path}' 不存在,请检查路径是否正确")

单元测试样例:
def test_read_text_not_found(self):
"""测试读取不存在的文件时的异常处理"""
with self.assertRaises(Exception) as context:
FileHandler.read_text("不存在的文件.txt")
# 验证异常信息是否包含预期提示
self.assertIn("文件 '不存在的文件.txt' 不存在,请检查路径是否正确", str(context.exception))
错误场景:用户误输入文件路径(如拼写错误、路径错误),例如将C:\test\orig.txt输入为C:\test\org.txt。
2. 权限不足异常
设计目标:当程序没有权限读取输入文件或写入结果文件时,捕获PermissionError并提示用户更换路径。
核心代码:

with open(file_path, 'w', encoding='utf-8') as file:
    file.write(f"{similarity_score:.2f}")

except PermissionError:
raise Exception(f"错误:没有权限写入文件 '{file_path}',请更换输出路径")
单元测试样例:
python
运行
def test_write_result_permission_error(self):
"""测试写入无权限路径时的异常处理"""
# 尝试写入系统保护目录(如Windows的C:\Windows)
with self.assertRaises(Exception) as context:
FileHandler.write_result("C:\Windows\result.txt", 0.85)
self.assertIn("没有权限写入文件", str(context.exception))
错误场景:用户将结果文件路径指定为系统保护目录(如C:\Windows)或其他无写入权限的位置。
3. 空文本输入异常
设计目标:当输入文件为空(或仅含空格、标点)时,明确提示用户检查文件内容,避免后续计算出现除以零等逻辑错误。
核心代码:
def preprocess(self, text):
if not text.strip():
raise ValueError("错误:输入文本为空,请检查文件内容")
# 其他预处理逻辑...
单元测试样例:

def test_preprocess_empty_text(self):
"""测试处理空文本时的异常处理"""
preprocessor = TextPreprocessor()
with self.assertRaises(ValueError) as context:
preprocessor.preprocess(" \n\t") # 仅含空白字符的文本
self.assertIn("输入文本为空,请检查文件内容", str(context.exception))
错误场景:用户提供的原文或抄袭版论文文件为空(可能是误操作删除了内容)。
4. 命令行参数错误
设计目标:当用户输入的命令行参数数量不正确时,立即提示正确用法,避免程序因参数缺失而运行失败。
核心代码:

if len(sys.argv) != 4:
print("用法错误:请提供3个参数")
print("正确格式:python main.py [原文文件路径] [抄袭版论文路径] [结果输出路径]")
sys.exit(1)
单元测试样例:

def test_command_line_args(self):
"""测试命令行参数数量错误时的处理"""
# 模拟参数不足的情况(仅2个参数)
original_argv = sys.argv
sys.argv = ["main.py", "orig.txt", "copy.txt"] # 缺少输出路径

with self.assertRaises(SystemExit) as context:
    main()
self.assertEqual(context.exception.code, 1)  # 验证程序退出码

# 恢复原始参数
sys.argv = original_argv

错误场景:用户运行程序时输入的参数数量不足(如仅提供原文和抄袭版路径,忘记输出路径)

三、异常处理的整体原则
明确性:异常信息需包含具体错误位置、和解决方案,避免模糊提示;
不掩盖错误:所有异常均会抛出并显示,确保用户知晓问题所在;
边界覆盖:覆盖文件操作、文本处理、参数校验等所有可能出错的环节,确保程序在各种异常下均能优雅退出。

posted @ 2025-09-23 20:50  sumayya  阅读(8)  评论(0)    收藏  举报