结对项目

这个作业属于哪个课程 软件工程
这个作业要求在哪里 作业要求
这个作业的目标 学会结对完成项目,加强团队合作
开发者姓名 陈霖昊(3123004346) 林裕秋(3123004794)
GitHub仓库 仓库链接

一、PSP2.1 个人软件过程阶段表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 40
• Estimate
估计这个任务需要多少时间 30 40
Development 开发 410 415
• Ana0lysis
需求分析(包括学习新技术) 60 60
• Design Spec
生成设计文档 20 20
• Design Review
设计复审 20 15
• Coding Standard
代码规范 10 10
• Design
具体设计 30 40
• Coding
具体编码 180 200
• Code Review
代码复审 30 60
• Test
测试与修改 60 60
Reporting 报告 140 150
• Test Report
测试报告 90 100
• Size Measurement
计算工作量 10 10
• Postmortem
事后总结与改进计划 40 40
合计 580 605

二、解析与代码说明


代码结构设计

项目采用模块化设计,各文件职责明确:

  • equation.py
    核心数据结构定义:
    • Operators运算符枚举(ADD/SUB/MUL/DIV),封装优先级、运算逻辑。
    • Numeric分数类,支持带分数、约分、运算,确保计算唯一性。用来存操作数,所有生成的数值表示为分子和分母两部分。比如1是1/1,0.5是1/2.
    • EquationNode表达式树节点,递归构建/求值/标准化/去重。主要包括
    • EquationSet:管理题目集合与答案映射。
  • file_processor.py
    文件读写与题目解析:
    • question_read()正则表达式拆分双栈法构建表达式树
    • equations_write():将题目与答案按格式写入文件。
  • generate.py
    随机生成题目:
    • Generator深度递归随机生成表达式树标准化去重 → 生成合法题目。
  • main.py
    CLI入口:
    • 命令行解析分发至生成/批改模式 → 调用对应模块完成功能。

关键代码流程解析

1. 表达式树构建与去重
# equation.py - EquationNode 关键方法
def normalize(self):
    # 递归标准化子树:左子树优先级>右子树 → 消除交换律重复
    if self.left: l_order = self.left.normalize()
    if self.right: r_order = self.right.normalize()
    if l_order < r_order: self.left, self.right = self.right, self.left  # 保证左大右小
def compare(self, other_node):
    # 递归比较结构是否等价(考虑运算符交换律)
    if Operators.ADD/MUL:允许左右子树互换比较 → 结构性等价判断
2. 分数运算与化简逻辑
# Numeric 类关键方法
def simplify(self):
    gcd = greatest_common_divisor(numerator, denominator)  # 最大公约数化简
def evaluate(self, value1, value2, op):
    # 四则运算规则处理分母通分、运算
    if OP为加减 → 求分母最小公倍数 → 分子运算后化简
3. 题目生成逻辑
# generate.py - Generator类
def generate_equation_tree(depth):
    if depth == 0: return Numeric叶子节点
    else: 随机选择运算符 → 递归生成左右子树 → 保证操作数限制

三、效能分析

  • 性能图
  • 覆盖率测试

四、测试运行

1、文件读取测试(TestFileRead)
验证正常文件内容读取
检测不存在的文件处理(触发FileNotFoundError)
处理目录路径(返回None)

class TestFileRead(unittest.TestCase):
    def test_read_existing_file(self):
        with tempfile.NamedTemporaryFile(mode='w+', delete=False, encoding='utf-8') as tmp:
            tmp.write("test content")
            tmp_path = tmp.name
        self.assertEqual(file_read(tmp_path), "test content")
        os.remove(tmp_path)

    def test_file_not_found(self):
        with self.assertRaises(FileNotFoundError):
            file_read("nonexistent_file.txt")

    def test_read_directory(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            self.assertIsNone(file_read(tmpdir))

2、文件追加写入测试(TestFileWrite)
验证数据正确追加到文件末尾

class TestFileWrite(unittest.TestCase):
    def test_append_to_file(self):
        with tempfile.NamedTemporaryFile(mode='w+', delete=False, encoding='utf-8') as tmp:
            tmp_path = tmp.name
        file_write(tmp_path, "new data")
        with open(tmp_path, 'r', encoding='utf-8') as f:
            self.assertIn("new data", f.read())
        os.remove(tmp_path)

3、数值转换测试(TestSingleNumericRead)
整数转换(5 → 5/1)
分数转换(3/4 → 3/4)
带分数转换(1'1/3 → 4/3)
非法格式处理(返回None)

class TestSingleNumericRead(unittest.TestCase):
    def test_integer(self):
        num = single_numeric_read("5")
        self.assertEqual(num.numerator, 5)
        self.assertEqual(num.denominator, 1)

    def test_fraction(self):
        num = single_numeric_read("3/4")
        self.assertEqual(num.numerator, 3)
        self.assertEqual(num.denominator, 4)

    def test_mixed_number(self):
        num = single_numeric_read("1'1/3")
        self.assertEqual(num.numerator, 4)
        self.assertEqual(num.denominator, 3)

    def test_invalid_format(self):
        self.assertIsNone(single_numeric_read("invalid"))

4、答案文件解析测试(TestAnswerRead)
正确解析包含多种数值格式的答案文件

class TestAnswerRead(unittest.TestCase):
    def test_answer_parsing(self):
        with tempfile.NamedTemporaryFile(mode='w+', delete=False, encoding='utf-8') as tmp:
            tmp.write("1. 5\n2. 3/4\n3. 1'1/3\n")
            tmp_path = tmp.name
        answers = answer_read(tmp_path)
        self.assertEqual(len(answers), 3)
        self.assertEqual(answers[0].numerator, 5)
        self.assertEqual(answers[1].denominator, 4)
        os.remove(tmp_path)

5、题目文件解析测试(TestQuestionRead)
验证数学表达式转换为EquationNode并正确计算

class TestQuestionRead(unittest.TestCase):
    def test_expression_parsing(self):
        with tempfile.NamedTemporaryFile(mode='w+', delete=False, encoding='utf-8') as tmp:
            tmp.write("1. 3 + 4 * 2\n")
            tmp_path = tmp.name
        questions = question_read(tmp_path)
        self.assertEqual(len(questions), 1)
        # 假设EquationNode有evaluate方法
        result = questions[0].evaluate()
        self.assertEqual(result.numerator, 11)
        os.remove(tmp_path)

6、语法树构建测试(TestBuildTree)
确保操作符栈能正确生成表达式树

class TestBuildTree(unittest.TestCase):
    def test_build_addition_tree(self):
        op_stack = ['+']
        num_stack = [
            eq.EquationNode(eq.Numeric(1, 3), None, None),
            eq.EquationNode(eq.Numeric(1, 4), None, None)]
        build_tree(op_stack, num_stack)
        self.assertEqual(len(num_stack), 1)
        self.assertEqual(num_stack[0].value, eq.Operators.ADD)

7、题目生成测试(TestGenerate)
验证生成指定数量的数学题目及答案
检查文件写入流程完整性

class TestGenerate(unittest.TestCase):
    def setUp(self):
        self.test_dir = tempfile.TemporaryDirectory()
        self.test_files = {
            'question.txt': '',
            'answer.txt': '',
            'exercise.txt': '',
        }
        for filename, content in self.test_files.items():
            path = os.path.join(self.test_dir.name, filename)
            with open(path, 'w', encoding='utf-8') as f:
                f.write(content)

    def test_generate(self):
        question_path = os.path.join(self.test_dir.name, 'question.txt')
        answer_path = os.path.join(self.test_dir.name, 'answer.txt')
        exercise_path = os.path.join(self.test_dir.name, 'exercise.txt')

        es = ge.generate_equations(10, 4, 2,exercise_path)
        es.print_equation_set()
        for answer in es.answer_dict.values():
            fp.file_write(answer_path, str(answer) + '\n')

        n = 0
        for equation in es.equation_list:
            fp.equation_write(equation, question_path, n)
            n += 1

        self.clean_up()

    def clean_up(self):
        self.test_dir.cleanup()

8、批改功能测试(TestProofs)
集成测试题目验证流程
验证成绩文件生成机制

class TestProofs(unittest.TestCase):
    def setUp(self):
        self.test_dir = tempfile.TemporaryDirectory()
        self.test_files = {
            'question.txt': """12. 1 / 1/8 + 1 + 1/2
13. 4 - 2 + 1'1/2 + 0
14. ( 5 * 3 ) * ( 8 - 0 )
15. 7 + 0 + 2/3 + 2/7
16. ( 4 * 1 ) * ( 7 + 2 )""",
            'answer.txt': """11.  3
12.  9'1/2
13.  3'1/2
14.  120
15.  7'20/21""",
            'grade.txt': '',
        }
        for filename, content in self.test_files.items():
            path = os.path.join(self.test_dir.name, filename)
            with open(path, 'w', encoding='utf-8') as f:
                f.write(content)

    def test_proofs(self):
        question_path = os.path.join(self.test_dir.name, 'question.txt')
        answer_path = os.path.join(self.test_dir.name, 'answer.txt')
        grade_path = os.path.join(self.test_dir.name, 'grade.txt')

        m.proofread_the_questions(question_path, answer_path, grade_path)

        file_data = fp.file_open(grade_path)
        print(file_data.read())
        fp.file_close(file_data)

        self.test_dir.cleanup()

9、题目去重测试(TestDeduplication)
测试不同数学表达式在算术等价上的重复检测

class TestDeduplication(unittest.TestCase):
    def setUp(self):
        self.test_dir = tempfile.TemporaryDirectory()
        self.test_files = {
            'question1.txt': """1. 1 / 1/8 + 1 + 1/2
     2.4 - 2 + 1'1/2 + 0
     3.( 5 * 3 ) * ( 8 - 0 )
     4.7 + 0 + 2/3 + 2/7""",
            'question2.txt': """1. 1 / 1/8 + 1 + 1/2
     2.1'1/2 + 0 + 4 - 2 
     3.( 8 / 1'2/5 ) / ( 4 * 2'1/2 )
     4.( 6 * 2/10 ) * ( 9 - 7 )""",
        }
        for filename, content in self.test_files.items():
            path = os.path.join(self.test_dir.name, filename)
            with open(path, 'w', encoding='utf-8') as f:
                f.write(content)

    def test_deduplication(self):
        question_path1 = os.path.join(self.test_dir.name, 'question1.txt')
        question_path2 = os.path.join(self.test_dir.name, 'question2.txt')

        questions1 = fp.question_read(question_path1)
        questions2 = fp.question_read(question_path2)

        for q1 in questions1:
            if ge.deduplication(questions2, q1):
                print("重复")

        self.test_dir.cleanup()

10、主流程测试(TestMain)
验证完整的生成+批改端到端流程

class TestMain(unittest.TestCase):
    def test_main(self):
        m.proofread_the_questions(fp.const_question_path,
                            fp.const_answer_path,
                            fp.const_grade_path)

        ge.generate_equations(10, 4, 1000)

  • 单元测试结果
posted @ 2025-03-20 23:17  窗帘盒子  阅读(24)  评论(0)    收藏  举报