2023软件工程第二次作业(结对编程)

目录

一,团队简介及代码仓库项目地址

二,解题思路

三,设计实现过程

1.设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,

2.关键函数流程图

3.单元测试是怎么设计的,分析单元测试的合理性与充分性

四,项目代码及注释说明

五,程序改进及性能分析

六,PSP表格

七,每日软件工程学习日志

八,项目里程碑内容展示

九,解决项目的心路历程与收获

十,进阶结对项目


一,团队简介及代码仓库项目地址

本次作业小团队成员如下:吴烨凯M23181003,毛贻欢M23183901。

代码仓库项目地址为:海军工程大学-何智勇/Fundamental-Operations - 码云 - 开源中国 (gitee.com)

分工情况可于里程碑中任务分工里查看。

整个过程中团队的部分工作状态如下(可在对应社区动态中查看):

二,解题思路

整个题目的解题思路如下:

        首先思考的是如何进行四则运算程序的编写,即如何进行功能函数和主函数的编写,这也是最重要的地方,在这个步骤中,要思考各种客户需求、边界条件、约束条件,是整个项目中最主要的部分;

        还需要注意的点是答案检查和反馈: 用户提交答案后,程序会自动检查答案的正确性,并提供明确的反馈,显示哪些题目回答正确,哪些回答错误。这种实时反馈有助于用户了解他们的表现

        以及结果保存和导出: 该程序允许用户将生成的题目和正确答案保存到文件中,以供以后查看或导出。这对于教育或记录进度非常有用。

        当程序编写完毕后,还要设计相应的单元测试模块和性能测试模块,并通过单元测试和性能测试的结果来修改,升级代码,以及考虑整个过程中的模块化设计和编写,以便于后续的检查和更新。

三,设计实现过程

1.设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,

2.关键函数流程图

流程图如下所示。

关键函数代码将在第四大点中附出。

3.单元测试是怎么设计的,分析单元测试的合理性与充分性

单元测试代码如下

import unittest
from unittest import TestCase
from unittest.mock import patch
from function import *
class TestGenerateQuestions(TestCase):
    def test_generate_questions(self):
        # 测试generate_questions函数

        num_questions = 10
        questions = generate_questions(num_questions)

        # 测试题目数量与要求的数量是否相等
        self.assertEqual(len(questions), num_questions)

        for question in questions:
            expression, answer = question
            # 断言answer是一个浮点数并且大于等于0小于等于100
            self.assertIsInstance(answer, float)
            self.assertGreaterEqual(answer, 0)
            self.assertLessEqual(answer, 100)


class TestValidateAnswer(TestCase):
    def test_validate_answer_correct(self):
        # 测试validate_answer函数在用户回答正确时的行为

        question = ('2 + 3 + 4', 9)
        user_answer = '9'

        with patch('builtins.print') as mock_print:
            # 调用validate_answer函数
            validate_answer(question, user_answer)

        # 断言print函数被调用一次,并且输出的内容为"回答正确!"
        mock_print.assert_called_once_with("回答正确!")


    def test_validate_answer_zero_correct(self):
        # 测试validate_answer函数在用户回答0,且回答正确时的行为

        question = ('2*2-4', 0)
        user_answer = '0'

        with patch('builtins.print') as mock_print:
            # 调用validate_answer函数
            validate_answer(question, user_answer)

        # 断言print函数被调用一次,并且输出的内容为"回答正确!"
        mock_print.assert_called_once_with("回答正确!")

    def test_validate_answer_negative_answer(self):
            # 测试validate_answer函数在用户回答负数时的行为

            question = ('2 + 3 + 4', 9)
            user_answer = '-1'

            with patch('builtins.print') as mock_print:
                # 调用validate_answer函数
                validate_answer(question, user_answer)

            # 断言print函数被调用一次,并且输出的内容为"回答错误!正确答案是: 9"
            mock_print.assert_called_once_with("回答错误!正确答案是:", 9)
    def test_validate_answer_closetozero_correct(self):
        # 测试validate_answer函数在用户回答100,且回答正确时的行为

        question = ('2*2-3.9', 0.1)
        user_answer = '0.1'

        with patch('builtins.print') as mock_print:
            # 调用validate_answer函数
            validate_answer(question, user_answer)

        # 断言print函数被调用一次,并且输出的内容为"回答正确!"
        mock_print.assert_called_once_with("回答正确!")
    def test_validate_answer_hundred_correct(self):
        # 测试validate_answer函数在用户回答100,且回答正确时的行为

        question = ('2*2*25', 100)
        user_answer = '100'

        with patch('builtins.print') as mock_print:
            # 调用validate_answer函数
            validate_answer(question, user_answer)

        # 断言print函数被调用一次,并且输出的内容为"回答正确!"
        mock_print.assert_called_once_with("回答正确!")

    def test_validate_answer_lesshundred_correct(self):
        # 测试validate_answer函数在用户回答99.9,且回答正确时的行为

        question = ('4*25-0.1', 99.9)
        user_answer = '99.9'

        with patch('builtins.print') as mock_print:
            # 调用validate_answer函数
            validate_answer(question, user_answer)

        # 断言print函数被调用一次,并且输出的内容为"回答错误!正确答案是: 9"
        mock_print.assert_called_once_with("回答正确!")


    def test_validate_answer_incorrect(self):
        # 测试validate_answer函数在用户回答错误时的行为

        question = ('2 + 3 + 4', 9)
        user_answer = '8'

        with patch('builtins.print') as mock_print:
            # 调用validate_answer函数
            validate_answer(question, user_answer)

        # 断言print函数被调用一次,并且输出的内容为"回答错误!正确答案是: 9"
        mock_print.assert_called_once_with("回答错误!正确答案是:",9)


    def test_validate_answer_out_of_range_answer(self):
        # 测试validate_answer函数在用户回答超出范围时的行为

        question = ('2 + 3 + 4', 9)
        user_answer = '101'

        with patch('builtins.print') as mock_print:
            # 调用validate_answer函数
            validate_answer(question, user_answer)

        # 断言print函数被调用一次,并且输出的内容为"回答错误!正确答案是: 9"
        mock_print.assert_called_once_with("回答错误!正确答案是:",9)

    def test_validate_answer_decimal_answer(self):
        # 测试validate_answer函数在用户回答小数时的行为

        question = ('2 + 3 + 4', 9)
        user_answer = '9.5'

        with patch('builtins.print') as mock_print:
            # 调用validate_answer函数
            validate_answer(question, user_answer)

        # 断言print函数被调用一次,并且输出的内容为"回答错误!正确答案是: 9"
        mock_print.assert_called_once_with("回答错误!正确答案是:",9)




    def test_validate_answer_whitespace_answer(self):
        # 测试validate_answer函数在用户回答答案包含空格时的行为

        question = ('2 + 3 + 4', 9)
        user_answer = '   9   '

        with patch('builtins.print') as mock_print:
            # 调用validate_answer函数
            validate_answer(question, user_answer)

        # 断言print函数被调用一次,并且输出的内容为"回答正确!"
        mock_print.assert_called_once_with("回答正确!")




if __name__ == '__main__':
    unittest.main()

单元测试结果如下:

本次单元测试遵循合理性和充分性原则,测试用例采用了正面测试及边界值分析。

单元测试用例分析
1.正面测试(Positive Testing)
测试被测对象的正确功能实现无误,即正常流程功能。给出一道答案为9的题目,并分别测试正确回答和错误回答情况,以及用户回答是小数时,测试validate_answer函数在的行为。

2. 边界值分析法
要求题目的答案是在0到100范围之间,所以测试当题目答案分别为-1,0,0.1,99.9,100,101时的情况。

四,项目代码及注释说明

关键代码如下:

import random
import math
import decimal
def generate_question():
  #生成一个四则运算
  operators = ['+', '-', '*', '/']
  while True:
    num1 = random.randint(1, 100)               # 随机生成第一个运算数
    num2 = random.randint(1, 100)               # 随机生成第二个运算数
    num3 = random.randint(1, 100)               # 随机生成第三个运算数
    operator1 = random.choice(operators)              # 随机生成第一个运算符
    operator2 = random.choice(operators)              # 随机生成第二个运算符
    expression = str(num1) + " " + operator1 + " " + str(num2) + " " + operator2 + " " + str(num3)
    answer =  int(eval(expression) *100+0.5)/100      #计算题目答案,并四舍五入保留两位小数
    if 0 <= answer <= 100:                            #当题目答案不在0到100时重新生成题目
        return expression, answer                     #返回题目和答案

def generate_questions(num_questions):
    questions = []               #定义questions列表
    for _ in range(num_questions):
        expression, answer = generate_question()
        questions.append((expression, answer))   #将题目的expression和answer以元组的形式添加到题目列表中
    return questions

def validate_answer(question, user_answer):    #判断自己给的答案是否正确
    expression, correct_answer = question
    if float(user_answer) == correct_answer:
        print("回答正确!")
    else:
        print("回答错误!正确答案是:" ,correct_answer)

说明分析:

        首先生成题目,将题目分解为生成三个随机数和两个随机四则运算符,并将题目组合显示,同时计算正确答案,保留题目和答案。

        按用户输入的要求的最大值和题目数量为界,将上一步骤生成的随机题目和对应答案以元组的形式储存。

        最后用户输入答案并判断答案是否正确返回对应提示。

五,程序改进及性能分析

使用pycharm内置的Profile对代码进行性能分析

通过性能分析,发现当生成题目并验证答案时占用较多时间,generate_questions函数采用了线性搜索的方式,如果生成的题目answer不在0到100范围内,会一直重新调用generate_question函数重新生成题目,这可能会导致性能问题。可以将对题目的answer的范围验证放到generate_question函数中,这可以减少不必要的重复生成和函数调用,在一定程度上可以提高代码的效率。以下是改进后的代码。

六,PSP表格

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划100150
· Estimate· 估计这个任务需要多少时间100150
Development开发9501160
· Analysis· 需求分析 (包括学习新技术)120200
· Design Spec· 生成设计文档100120
· Design Review· 设计复审8060
· Coding Standard· 代码规范 (为目前的开发制定合适的规范)5060
· Design· 具体设计120180
· Coding· 具体编码200280
· Code Review· 代码复审10060
· Test· 测试(自我测试,修改代码,提交修改)180200
Reporting报告110150
· Test Repor· 计算工作量3050
· Size Measurement· 事后总结, 并提出过程改进计划80100
· 合计11601460

        由上表我们可以看出实际耗时往往比预估耗时的时间要长,原因可能是由于对整个流程并不是特别熟悉,个人的能力较为有限以及在编程过程中会遇到一些没有考虑到的新问题。在后续设计中,应充分考虑此类原因再进行预估耗时的设计。

七,每日软件工程学习日志

10.13 进行项目计划,估计耗时以及团队分工。

10.14 需求分析以及相关设计文档,规范的制定及仓库的连接,调试。

10.15 10.16 第一阶段的代码具体编写。

10.17 10.18 10.19 深入学习unittest单元测试相关理论知识,编写单元测试代码以及性能测试代码,完成第二阶段代码具体编写。

代码编写阶段为10.15-10.19,由于让代码运行、运行后的测试、维护都要不停调试,因而时间花费较长。但是由于任务时间有限,能力水平有限,程序并不是尽善尽美,但是已经尽我们所能认真对待。

查漏补缺阶段为10.19,在此阶段对所有任务进行最后检查及修改。

其中里程碑的记录、博客撰写贯穿于学习阶段始终。

八,项目里程碑内容展示

在里程碑中,共有五条需求以及对应的任务,其中三条是子需求,任务项与需求项相关联。

九,解决项目的心路历程与收获

        当接到第二次作业的任务时,是十分惆怅的,感觉任务量较第一次有了质的增多,并且对于编程能力的要求也有了质的提高,整个任务看起来十分复杂无从下手,好在团队成员之间有进行充分沟通交流,在梳理了任务流程后,便开始了分工协作,整体上也在有序进展。当完成整个项目时,内心是十分有成就感和自豪感的。

        本次结对编程的任务,让我了解了结对编程的概念,第一次直面感受到了它的魅力。在这里,便说一下我和我搭档在整个过程中的收获:

        首先,在编程上,由于是结对编程,可以相互检查相互学习,许多小错误得以避免、灵感来源也更加丰富。在一个人编程时,有的时候可能会犯一些语法、变量名拼写错误以及被一些点困惑,在结对编程中,暂时不写代码的人便能通过观察指出这些小问题以及提供一些新的思路方法,减少了单人编程时因为一些“莫名其妙”的小问题所浪费的时间。

        其次,结对编程比一个人编程效率更高,更省力。每个人都可以在自己所擅长的领域大放异彩,作业的质量也有了更好的提升。同时,当一个人编程乏力之后,或者编写乏力时,另一个人也可以进行临时替换,节省了总体时间,同时,当主任务员得到一定休息后,思路也会更加清晰,提高了编程效率及编程质量。

        另外,结对编程的两个人可以互相学习。一是学习思想。面对同样的问题 不同的人可能有不同的想法,可以相互学习彼此的思路;二是取长补短。每个人擅长的领域不一样,对于自己不擅长的可以向对方虚心请教,不断提升个人综合实例,比如我向他学习如何更好地编程,如何提高自己的学习编程能力。

      最后,在结对编程中非常重要的一点是,无论遇到任何问题,都是一个小团队共同面对,两个人的互相激励与督促也能让解决问题变得更简单,使任务更快的完成。当自己快想放弃时,看到自己的搭档还在努力想要解决bug,自然而然的也会重新调整,继续想办法解决问题。

十,进阶结对项目

        第二阶段中,我们组选择的题目是能够支持分步骤答题,并按步骤判断对错。

        代码如下:

import random
import math
import decimal


def generate_question(c):
    # 生成一个四则运算
    operators = ['+', '-', '*', '/']
    while True:
        num1 = random.randint(1, 100)  # 随机生成第一个运算数
        num2 = random.randint(1, 100)  # 随机生成第二个运算数
        num3 = random.randint(1, 100)  # 随机生成第三个运算数
        operator1 = random.choice(operators)  # 随机生成第一个运算符
        operator2 = random.choice(operators)  # 随机生成第二个运算符
        expression = str(num1) + " " + operator1 + " " + str(num2) + " " + operator2 + " " + str(num3)
        if c== 1:
          if (operator1 == '+' or operator1 == '-') and (operator2 == '*' or operator2 == '/'):
            answ = int(eval(str(num2) + " " + operator2 + " " + str(num3)) * 100 + 0.5) / 100
            expression1 = str(num1)+operator1+str(answ)
          else:
            answ=int(eval(str(num1) + " " + operator1 + " " + str(num2)) * 100 + 0.5) / 100
            expression1 = str(answ)+operator2+str(num3)
          answer = int(eval(expression1) * 100 + 0.5) / 100
        elif c==0:
          expression1=" "
          answer = int(eval(expression) * 100 + 0.5) / 100  # 计算题目答案,并四舍五入保留两位小数
        if 0 <= answer <= 100:  # 当题目答案不在0到100时重新生成题目
            return expression,expression1,answer  # 返回题目和答案


def generate_questions(num_questions,flag):
    c=flag
    questions = []  # 定义questions列表
    for _ in range(num_questions):
        expression, expression1,answer = generate_question(c)
        questions.append((expression, expression1,answer))  # 将题目的expression和answer以元组的形式添加到题目列表中
    return questions
def expression_to_file(questions, filename):
    with open(filename, 'w') as file:
        for question in questions:
            file.write(question[0] + '\n')
def answer_to_file(questions, filename):
    with open(filename, 'w') as file:
        for question in questions:
            file.write(str(question[2]) + '\n')

def answers_to_file(questions, filename):
    with open(filename, 'w') as file:
        for question in questions:
            file.write(str(question[1]) + "="+str(question[2])+'\n')
def validate_answer(question, user_answer):  # 判断自己给的答案是否正确
    expression, expression1,correct_answer = question
    if float(user_answer) == correct_answer:
        print("回答正确!")
    else:
        print("回答错误!正确答案是:", correct_answer)
def validate1_answer(question, user_answer):  # 判断自己给的答案是否正确
    expression, expression1,correct_answer = question
    correct=str(expression1+"="+str(correct_answer))
    if user_answer == correct:
        print("回答正确!")
    else:
        print("回答错误!正确答案是:", correct)

        通过用户输入1或者0来决定是否要进行分步答题,1 为分步答题,0为直接答题,并将题目和答案都写于文本中供查阅。

        在分步答题中,还是通过元组添加到题目列表中并判断结果是否正确,遇到的最大的问题是在分步答题中,要考虑运算的优先级来进行第一步计算并判断正误。

        由于在第一阶段进行了单元测试和性能测试保证了代码的可读性、可用性,以及第一阶段的代码在编写过程中是以模块为单位编写,遵循了单一职责原则(一个类只做一件事)、里式替换原则(子类可以扩展父类)、遵循了开闭原则(关闭修改,开放新增)等原则,使得在第二阶段中代码具有较好的可扩展性和可维护性:

  • 需求变化时是通过新增而不是修改已有代码实现,这样保证了代码稳定性,易于实现新功能,避免了牵一发而动全身。
  • 事先定义了代码框架,扩展也是根据框架扩展,体现了用抽象构建框架,用实现扩展细节,保证了稳定性也保证了灵活性。
  • 在进一步的编写过程中仍然遵循着开闭原则。便于在后续客户使用中若提出更多需求时,可以方便进行新增代码来实现新的需求。

        10.28 对结对编程程序进行进一步改进,增加了打分功能以及支持同优先级的分步计算,运行结果如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值