第一次个人编程作业

这个作业属于哪个课程 软件工程2024 (广东工业大学)
这个作业要求在哪里 个人项目
这个作业的目标 1. 完成个人项目:设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
2. 学习设计项目的PSP表格
3. 学习使用github对代码进行管理
4. 学习使用性能分析工具,分析代码的性能
5. 学习如何进行单元测试,追求高代码覆盖率

项目github地址: https://github.com/Lghhhhhhhh/3122004619/tree/master1

PSP表格

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

知识点:

主要思想:

  • 利用jieba中文分词库对输入地址的文本进行中文词提取,两文本的词列表去重合并后,获取两文本的词频向量,计算出向量的余弦相似度,从而得到文本相似度
  • 思路依据:使用余弦相似度计算文本相似度

计算模块接口的设计与实现过程

  • 实现过程图:
  • 计算类的内部实现:
  • 项目内部实现图(pycharm自带):

计算模块接口部分的性能改进

  • 本程序的计算模块的主要性能瓶颈为文本的输入
  • 对于短文本而言,python文件IO类的read()方法可以流程输入,但是对于较长文本的输入,则需要使用readlines()方法实现逐行输入,避免因内存容量导致的IO速率过慢
  • 输入模块部分代码:
        try:
            with open(original_text_address, "r", encoding="utf-8") as file1:

                # self.original_text = file1.read()
                self.original_list = file1.readlines()
                self.original_text = self.original_text.join(self.original_list)
            except FileNotFoundError:
                # 提示该文件不存在
                print("原文件 " + original_text_address + " 文件不存在")
                original_text_address = ""
  • 性能分析图(由pycharm中的cprofile生成):
  • 程序消耗时间:

计算模块部分单元测试展示:

  • 本项目单元测试用了pycharm自带的unittest测试框架,运动unittest框架的TestCase类,在类中实例化测试函数,运行测试python文件,实现main程序的测试。
  • 值得关注的是,由于本项目带有输入模块,所以在测试过程中,需要引入unittest测试框架中的patch类模拟用户的输入(单输入,多输入均可),实现输入模块的测试。
  • 测试思路:单元测试通过模拟用户的行为,以及对于main.py中DuplicateChecking查重类中的每个函数运行流程进行模拟,使用框架自带的断言函数assertEqual(),实现对项目中的类及其方法的测试。
  • 测试代码:
import unittest
import random
from main import DuplicateChecking
from unittest.mock import patch  # 用于模拟输入
# 记录测试文本地址
original_text = [r'C:\Users\Lenovo\Desktop\测试集\orig.txt', r'C:\Users\Lenovo\Desktop\测试集\短orig.txt', '666', '777',
                 '888', '999']
test_text = [r'C:\Users\Lenovo\Desktop\测试集\orig_0.8_add.txt',
             r'C:\Users\Lenovo\Desktop\测试集\orig_0.8_del.txt',
             r'C:\Users\Lenovo\Desktop\测试集\orig_0.8_dis_1.txt',
             r'C:\Users\Lenovo\Desktop\测试集\orig_0.8_dis_10.txt',
             r'C:\Users\Lenovo\Desktop\测试集\orig_0.8_dis_15.txt',
             r'C:\Users\Lenovo\Desktop\测试集\短compare.txt',
             '666', '777', '888', '999']


class MyTestCase(unittest.TestCase):
    @patch('builtins.input')
    def test_IO(self, mock_input):
        result = DuplicateChecking()# 实例化测试对象
        mock_input.side_effect = [original_text[0], test_text[random.randint(0, 4)]]  # 正确的输入
        self.assertEqual(result.read_file(), True)# 断言测试判断
        mock_input.side_effect = [original_text[1], test_text[5]]  # 正确的输入
        self.assertEqual(result.read_file(), True)
        mock_input.side_effect = [original_text[random.randint(2, 5)], test_text[random.randint(6, 9)]]  # 错误的输入
        self.assertEqual(result.read_file(), False)

    @patch('builtins.input')
    def test_long_text_preprocess(self, mock_input):
        result = DuplicateChecking()
        mock_input.side_effect = [original_text[0], test_text[random.randint(0, 4)]]  # 正确的输入
        result.read_file()
        self.assertEqual(result.long_text_preprocess(), True)

    def test_short_text_preprocess(self):
        result = DuplicateChecking()
        result.original_text = "废话覅哦说不定v哦i被释冯绍峰放北京库房不玩"
        result.compare_text = "我IC呢嫩IC那我可浪放放瑟夫费钱农村"
        self.assertEqual(result.short_text_preprocess(), True)

    @patch('builtins.input')
    def test_text_checking(self, mock_input):
        result = DuplicateChecking()
        mock_input.side_effect = [original_text[0], test_text[random.randint(0, 4)],
                                  r'C:\Users\Lenovo\Desktop\测试集\查重数据.txt']  # 正确的输入
        self.assertEqual(result.text_checking(), True)

        mock_input.side_effect = [original_text[1], test_text[5],
                                  r'C:\Users\Lenovo\Desktop\测试集\查重数据.txt']  # 正确的输入
        self.assertEqual(result.text_checking(), True)

        mock_input.side_effect = [original_text[random.randint(2, 5)], test_text[random.randint(6, 9)],
                                  r'C:\Users\Lenovo\Desktop\测试集\查重数据.txt']  # 错误的输入
        self.assertEqual(result.text_checking(), False)


if __name__ == '__main__':
    unittest.main()
  • 测试覆盖率:(main.py没100%原因:查重数据文件输出的IO错误在测试中无法体现,需要计算机内存不足才能复现,需要危险的测试环境,所以没做该分支测试...)

计算模块部分异常处理说明

  • 异常处理模块主要只有文件输入输出的IO异常抛出处理
  • 输入异常(文件不存在):
        try:
            with open(original_text_address, "r", encoding="utf-8") as file1:

                # self.original_text = file1.read()
                self.original_list = file1.readlines()
                self.original_text = self.original_text.join(self.original_list)
            except FileNotFoundError:
                # 提示该文件不存在
                print("原文件 " + original_text_address + " 文件不存在")
                original_text_address = ""
  • 输出异常(文件输出(创建)失败):
        try:
            with open(duplicate_data_address, "w", encoding="utf-8") as file:
                file.write("查重文本与原文本相似度为:" + str(round(cos_sim, 2)))
                print("查重数据已经输入到文件中!!!")
            except IOError:
                print("查重数据文件创建失败,请重启程序!!!")

感悟:

  • 本次项目给我最大的收获就是学习了如何进行项目的单元测试,对于这次项目之前的测试,像大一的c语言课设,项目测试都是简单的走一遍程序流程,在输入框里面进行简单的测试,而本次项目接触到了一些专业化的工具,可以更好地进行代码覆盖率的测试。
  • 而我在本次项目中有做的不足的地方,如单元测试没有在完成每个功能函数的时候都进行,而是在整个项目几乎块完成的时候,才学习如何进行单元测试,导致后期消耗时间过长,且自身思维变得混乱,学习到了单元测试应该和功能代码编写同时进行这一教训。

参考博客:

posted @ 2024-03-13 20:32  刘国浩  阅读(102)  评论(0)    收藏  举报