软件工程结对项目 3:python实现自动生成小学四则运算题目的程序

这个作业属于哪个课程 广工计院计科34班软工
这个作业要求在哪里 作业要求
团队成员1 庄崇立3122004633
团队成员2 罗振烘3122004748
这个作业的目标 结对合作完成小学四则运算题目的程序,熟悉项目开发流程,提高团队合作能力

一、GitHub地址

二、需求

1.题目:实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能)。
2.说明:
自然数:0, 1, 2, …。
真分数:1/2, 1/3, 2/3, 1/4, 1’1/2, …。
运算符:+, −, ×, ÷。
括号:(, )。
等号:=。
分隔符:空格(用于四则运算符和等号前后)。
算术表达式:
e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),
其中e, e1和e2为表达式,n为自然数或真分数。
四则运算题目:e = ,其中e为算术表达式。
3.需求:
使用 -n 参数控制生成题目的个数,例如
Myapp.exe -n 10
将生成10个题目。
使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如
Myapp.exe -r 10
将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。
生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。
每道题目中出现的运算符个数不超过3个。
程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,23 + 45 = 和45 + 23 = 是重复的题目,6 × 8 = 和8 × 6 = 也是重复的题目。3+(2+1)和1+2+3这两个题目是重复的,由于+是左结合的,1+2+3等价于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重复的两道题,因为1+2+3等价于(1+2)+3,而3+2+1等价于(3+2)+1,它们之间不能通过有限次交换变成同一个题目。
生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:
四则运算题目1
四则运算题目2
……
其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。
在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:
答案1
答案2
特别的,真分数的运算如下例所示:1/6 + 1/8 = 7/24。
程序应能支持一万道题目的生成。
程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:
Myapp.exe -e .txt -a .txt
统计结果输出到文件Grade.txt,格式如下:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。

三、PSP

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

四、程序设计实现过程

1,框架设计

  • 这个项目采用了模块化的设计,将功能逻辑、用户界面和程序入口分离,形成了清晰的三层结构

1.1,核心功能模块(function.py):

  • CustomMathError 类:自定义异常类,用于处理特定的数学相关错误。
  • Number 类:表示一个数字,包括整数部分和分数部分。它能够生成随机数,并且可以简化分数。
  • Fraction 类:处理两个 Number 对象之间的分数运算,支持加减乘除。
  • Expression 类:生成和计算数学表达式,是整个程序的核心。

1.2,用户界面模块(interface.py):

  • MathQuizApp 类:创建图形用户界面,管理用户交互和显示结果。

1.3,主程序入口(main.py):

  • 创建 Tkinter 根窗口并启动应用。

1.4,三个模块之间的关系

  • main.py 作为程序入口,它导入并实例化 interface.py 中的 MathQuizApp 类。
  • MathQuizApp 类在生成题目时,会创建并使用 function.py 中的 Expression 类。
  • Expression 类内部使用 Number 和 Fraction 类来生成和计算表达式。

2,代码部分

  • 我们更深入地分析项目的关键代码

2.1,function.py 中的 Number 类

  • python核心代码如下
class Number:
    def __init__(self, max: int = None, nums: tuple = None):
        """
        初始化一个Number对象
        :param max: 随机生成数字时的最大值
        :param nums: 用于直接指定数字的组成部分(整数,分子,分母)
        """
        try:
            if nums:
                # 如果提供了nums元组,直接用它来初始化数字
                if isinstance(nums, tuple) and len(nums) == 3:
                    if nums[2] == 0:
                        raise ValueError("分母不能为零。")
                    else:
                        self.integer = int(nums[0])  # 整数部分
                        self.numerator = int(nums[1])  # 分子
                        self.denominator = int(nums[2])  # 分母
                else:
                    raise ValueError("nums必须是一个三元组(整数,分子,分母)。")
            else:
                # 如果没有提供nums,则随机生成一个数字
                if (not isinstance(max, int)) or max < 1:
                    raise ValueError("max必须大于等于1。")
                self.integer = random.randint(0, max-1)  # 随机生成整数部分
                self.denominator = random.randint(1, max-1)  # 随机生成分母
                is_fra = random.randint(1, 2)  # 随机决定是否生成分数部分
                if is_fra == 1:
                    self.numerator = random.randint(1, self.denominator)  # 生成分子
                else:
                    self.numerator = 0  # 不生成分数部分
                    # 计算生成的数值
                generated_value = self.integer + self.numerator / self.denominator
                # 如果生成的值大于等于 max,调整整数部分使其小于 max
                if generated_value >= max:
                    adjustment = int(generated_value // max)
                    self.integer -= adjustment
        except ValueError as e:
            raise CustomMathError(str(e))

    def reduce_fraction(self):
        """简化分数"""
        # 计算最大公约数
        gcd = math.gcd(self.numerator, self.denominator)
        numerator = self.numerator // gcd
        denominator = self.denominator // gcd
        # 处理假分数,将其转化为带分数
        integer = numerator // denominator + self.integer
        numerator = numerator % denominator
        self.integer = int(integer)
        self.numerator = int(numerator)
        self.denominator = int(denominator)

    def __str__(self):
        """将Number对象转换为字符串表示"""
        if self.integer == 0:
            if self.numerator:
                return f"{self.numerator}/{self.denominator}"
            else:
                return '0'
        else:
            if self.numerator:
                return f"{self.integer}'{self.numerator}/{self.denominator}"
            else:
                return str(self.integer)

    def __float__(self):
        """将Number对象转换为浮点数"""
        return self.integer + self.numerator / self.denominator
  • Number 类的设计很巧妙:

    • 它可以通过 max 参数随机生成一个不超过 max 的数,也可以通过 nums 元组直接指定数值。
    • 生成的数字包含整数部分和分数部分,这样可以生成更多样的题目。
    • reduce_fraction 方法用于简化分数,保证生成的题目更加规范。
  • 整体流程图如下

2.2,function.py 中的 Expression 类

  • python核心代码如下
class Expression:
     def generate_expression_list(self):
        """生成表达式列表"""
        expression_list = []
        sub_num = random.randint(1, 3)  # 随机决定子表达式的数量
        operators = [random.choice([' + ', ' - ', ' * ', ' % ']) for _ in range(sub_num)]
        subexpression_1 = self.generate_subexpression(1, operators[0])
        str_sub_1 = str(subexpression_1[0]) + subexpression_1[1] + str(subexpression_1[2])
        if sub_num == 1:
            expression_list.append(subexpression_1)
            str_sub_2 = ''
            str_sub_3 = ''
            string = str_sub_1 + '|' + str_sub_2 + '|' + str_sub_3
        elif sub_num == 2:
            subexpression_2 = self.generate_subexpression(2, operators[1])
            str_sub_2 = str(subexpression_2[0]) + str(subexpression_2[1])
            str_sub_3 = ''
            expression_list.append(subexpression_1)
            expression_list.append(subexpression_2)
            string = str_sub_1 + '|' + str_sub_2 + '|' + str_sub_3
        else:
            etype = random.randint(1, 2)
            subexpression_2 = self.generate_subexpression(etype, operators[1])
            if etype == 1:
                subexpression_3 = [operators[2]]
                str_sub_2 = str(subexpression_2[0]) + str(subexpression_2[1]) + str(subexpression_2[2])
                str_sub_3 = subexpression_3[0]
                if str_sub_1 >= str_sub_2 and operators[2] in [' + ', ' * ']:
                    expression_list.append(subexpression_1)
                    expression_list.append(subexpression_2)
                    expression_list.append(subexpression_3)
                    string = str_sub_1 + '|' + str_sub_2 + '|' + str_sub_3
                else:
                    expression_list.append(subexpression_2)
                    expression_list.append(subexpression_1)
                    expression_list.append(subexpression_3)
                    string = str_sub_1 + '|' + str_sub_2 + '|' + str_sub_3
            else:
                subexpression_3 = self.generate_subexpression(2, operators[2])
                str_sub_2 = str(subexpression_2[0]) + str(subexpression_2[1])
                str_sub_3 = str(subexpression_3[0]) + str(subexpression_3[1])
                expression_list.append(subexpression_1)
                expression_list.append(subexpression_2)
                expression_list.append(subexpression_3)
                string = str_sub_1 + '|' + str_sub_2 + '|' + str_sub_3
        return expression_list, string
  • 这个方法是生成表达式的核心:
    • 它随机决定生成1到3个子表达式。
    • 对于每个子表达式,它随机选择运算符,并调用 generate_subexpression 方法生成数字。
    • 它还处理了不同数量子表达式的组合方式,确保生成的表达式合理且多样。
  • 整体简化流程图如下

2.3,interface.py 中的 MathQuizApp 类

  • python核心代码如下
class MathQuizApp:
    def generate_questions(self):
        try:
            num_questions = self.validate_input(self.num_questions_entry.get())
            max_value = self.validate_input(self.max_value_entry.get())
            exp = Expression(max_value, num_questions)
            self.questions, self.answers = exp.run()
            self.questions_text.delete(1.0, tk.END)
            self.answers_text.delete(1.0, tk.END)
            for i, question in enumerate(self.questions, start=1):
                self.questions_text.insert(tk.END, f"{i}. {question}\n")
            with open("Exercises.txt", "w") as question_file:
                question_file.writelines(f"{q}\n" for q in self.questions)
            with open("Answer.txt", "w") as answer_file:
                answer_file.writelines(f"{a}\n" for a in self.answers)
        except ValueError as e:
            self.show_error(str(e))

    def check_answers(self):
        user_answers = self.answers_text.get(1.0, tk.END).splitlines()
        correct = []
        incorrect = []
        for i, (user_answer, correct_answer) in enumerate(zip(user_answers, self.answers), start=1):
            if user_answer.strip() == correct_answer.strip():
                correct.append(i)
            else:
                incorrect.append(i)
        self.results_text.delete(1.0, tk.END)
        self.results_text.insert(tk.END, f"正确题号: {', '.join(map(str, correct))}\n")
        self.results_text.insert(tk.END, f"错误题号: {', '.join(map(str, incorrect))}\n")
        with open("Grade.txt", "w") as grade_file:
            grade_file.write(f"Correct: {len(correct)} ({', '.join(map(str, correct))})\n")
            grade_file.write(f"Wrong: {len(incorrect)} ({', '.join(map(str, incorrect))})\n")
  • 这些方法展示了 GUI 的核心功能:
    • generate_questions 方法获取用户输入,使用 Expression 类生成题目,然后显示题目并保存到文件。
    • check_answers 方法比较用户答案和正确答案,显示结果并保存到文件。
  • 完整流程图如下

五、单元测试

1,测试部分概述

为保证该项目代码质量和正确运行,设计者编写了单元测试代码。本次单元测试使用了unittest框架实现对项目的单元测试。测试文件test_function.py内是对功能代码的测试,包含5条测试用例,测试文件test_interface.py内是针对用户图形化界面代码的测试,包含7条测试用例。总共12条测试用例

2,测试点

根据项目的代码整理出以下测试点

3,部分测试代码

test_function.py部分代码

import unittest
from function import Number, Fraction, Expression, CustomMathError

class TestMathFunctions(unittest.TestCase):
    def test_number_initialization(self):
        # Test 1: Initialize Number with max value
        num1 = Number(10)
        self.assertIsInstance(num1, Number)
        self.assertLessEqual(float(num1), 11)  # 检查是否小于等于 11

        # Test 2: Initialize Number with specific values
        num2 = Number(nums=(3, 1, 2))
        self.assertEqual(str(num2), "3'1/2")

test_interface.py部分代码

import unittest
from unittest.mock import patch, MagicMock
import tkinter as tk
from interface import MathQuizApp


class TestMathQuizApp(unittest.TestCase):
    def setUp(self):
        self.root = tk.Tk()
        self.app = MathQuizApp(self.root)

    def tearDown(self):
        self.root.destroy()

    @patch('interface.Expression')
    def test_generate_questions(self, mock_expression):
        # 测试用例 1: 正常生成题目
        mock_expression.return_value.run.return_value = (["1 + 1", "2 * 2"], ["2", "4"])
        self.app.num_questions_entry.insert(0, "2")
        self.app.max_value_entry.insert(0, "10")
        self.app.generate_questions()
        self.assertEqual(self.app.questions_text.get("1.0", tk.END).strip(), "1. 1 + 1\n2. 2 * 2")

    def test_validate_input(self):
        # 测试用例 2: 有效输入
        self.assertEqual(self.app.validate_input("5"), 5)

4, 运行测试用例

12条测试用例全部通过


六、性能分析

在用户图形化界面输入生成10000条题目,对此过程进行性能分析



根据执行结果可以得知,执行时间最长的函数为generate_questions,执行时间为599ms

七、项目总结

1,项目分工:

庄崇立:负责大部分主体代码和测试代码的编写
罗振烘:负责小部分代码编写和博客的编写

2,项目收获

2.1,技术方面的收获

  • 对 Python 语言的掌握更加深入。在项目过程中,我们运用了 Python 的各种特性,和各种库函数,提升了编程能力。
  • 学会了如何设计和实现一个具有特定功能的程序。从需求分析到算法设计,再到代码实现和测试,我们经历了完整的软件开发流程,积累了宝贵的实践经验。
  • 了解了如何进行程序优化。为了提高程序的性能和效率,我们不断尝试不同的方法,如优化算法、减少不必要的计算等,增强了问题解决的能力。

2.2,团队协作方面的收获

  • 高效沟通至关重要。在项目中,我们通过频繁的交流,及时分享彼此的想法和进展,避免了误解和重复工作,确保项目顺利进行。
  • 优势互补能提升整体效率。我们两人在不同方面各有优势,通过合理分工,充分发挥各自的长处,大大提高了项目的开发速度和质量。
  • 学会了互相支持和鼓励。在遇到困难时,我们互相提供帮助和建议,增强了团队的凝聚力。
posted @ 2024-09-17 12:16  xunlua  阅读(142)  评论(0编辑  收藏  举报