结对项目
这个作业属于哪个课程 | 计科22级12班软件工程 |
---|---|
这个作业要求在哪里 | 作业要求 |
这个作业的目标 | 尝试结对编程,深入熟悉开发过程,并设计一个能自动生成小学四则运算题,还能批改题目计算分数的程序 |
队伍成员 | 林楦(3222004851) 徐嘉炜(3122004838) |
Github链接:https://github.com/AAA-Lin/AAA-Lin.git
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 35 | 25 |
Estimate | 估计这个任务需要多少时间 | 800 | 600 |
Development | 开发 | 400 | 460 |
Analysis | 需求分析(包括学习新技术) | 200 | 160 |
Design Spec | 生成设计文档 | 40 | 40 |
Design Review | 设计审核 | 40 | 35 |
Coding Standard | 代码规范(为目前的开发制定合适的规范) | 20 | 40 |
Design | 具体设计 | 30 | 30 |
Coding | 具体编码 | 120 | 130 |
Code Review | 代码审核 | 50 | 40 |
Test | 测试(自我测试,修改代码,提交修改) | 200 | 190 |
Reporting | 报告 | 60 | 50 |
Test Report | 测试报告 | 25 | 30 |
Size Measurement | 计算工作量 | 30 | 30 |
Postmortem & Process Improvement Plan | 事后总结,并提出过程改进计划 | 30 | 30 |
合计 | 2080 | 1890 |
效能分析
优化思路
- 消耗时间最多函数:bracket_answer负责计算不同级别括号的表达式,它可能会消耗许多时间,尤其是在处理嵌套表达式时,所有首先考虑优化这个函数
- 分数:使用 Fraction 类处理分数会导致计算量较大。考虑简化分数和减少非必要的计算。
性能分析图
设计实现过程
代码组织
1. Operation类:
生成数学四则运算习题,将习题写入到"Exercise.txt"文件中,并将相应的答案写入"Answer.txt"文件中,其中
- __ init __:初始化习题数量和操作数范围的默认值。
- operation:生成习题和答案,并处理异常。
- combine:生成带有随机运算符和操作数的数学表达式。
- is_proper和get_proper_fraction:负责随机生成真分数。
- to_fraction:把不恰当的分数转换为带分数。
- bracket_insert:根据指定的括号参数在表达式中插入括号。
- get_answer和bracket_answer:计算答案,其中bracket_answer为计算带有括号的。
2. 重要函数: - check:检查新生成的表达式是否不在先前已经生成的表达式列表中。
- out_grade:读取习题和答案的对应文件,检查正确性,并返回正确和错误答案的摘要。
- main:它使用argparse来解析命令行参数。
3. 对应流程图:
代码说明
- 关键代码
class Operation:
def __init__(self):
self.number = 10
self.value = 10
def operation(self):
"""
生成四则运算函数,调用相关函数实现生成
:return: None
"""
f_exercise = open('Exercise.txt', 'a+', encoding='utf-8')
f_answer = open('Answer.txt', 'a+', encoding='utf-8')
f_exercise.seek(0)
f_answer.seek(0)
f_exercise.truncate()
f_answer.truncate()
count = 0
topic = []
ans = []
while True:
try:
exercise_list, answer = self.combine() # 控制运算符数量
except ZeroDivisionError: # 当0位除数 和 负数情况
continue
# True表示检查后无重复
if check(exercise_list, answer, topic, ans):
topic.append(str("".join(exercise_list)))
f_exercise.write("四则运算题目" + str(count + 1) + ": " + ' '.join(exercise_list) + ' =\n')
if re.search('/', answer):
d, n = answer.split('/')
ans.append(answer)
if int(d) > int(n):
answer = to_fraction(answer)
f_answer.write("答案" + str(count + 1) + ": " + answer + '\n')
count += 1
if count == self.number:
break
f_exercise.close()
f_answer.close()
- 思路:先实现输出写死的运算式到文件,然后通过不断实现对应功能函数来对写死的运算式修改实现随机出题,并计算答案。
- 注释说明:代码中已有注释说明
测试运行
测试说明
1. test_main_n_r:测试-n 100 -r 100
@mock.patch("Myapp.add_parm")
def test_main_n_r(self, mock_args):
mock_args.return_value = Namespace(answer_file=None, exercise_file=None, range=100, sum=100)
main()
exercise_file = open("Exercise.txt", "r", encoding='utf-8')
exercise_lines = exercise_file.readlines()
answer_file = open("Answer.txt", "r", encoding='utf-8')
answer_lines = answer_file.readlines()
exercise_file.close()
answer_file.close()
self.assertEqual(len(exercise_lines), 100)
self.assertEqual(len(answer_lines), 100)
**2. test_main_e_a:测试-e Exercise.txt -a Answer.txt **
@mock.patch("Myapp.add_parm")
def test_main_e_a(self, mock_args):
mock_args.return_value = Namespace(answer_file="Answer.txt", exercise_file="Exercise.txt", range=None, sum=None)
main()
grade_file = open("Grade.txt", "r", encoding='utf-8')
grade_lines = grade_file.readlines()
grade_file.close()
self.assertEqual(grade_lines[1], "Wrong:0 ()")
3. test_main_none:无参数时
@mock.patch("Myapp.add_parm")
def test_main_none(self, mock_args):
mock_args.return_value = Namespace(answer_file=None, exercise_file=None, range=None, sum=None)
self.assertEqual(main(), ValueError)
4. test_out_grade:测试答案中的对错情况
def test_out_grade(self):
grade = out_grade("Exercise.txt", "Answer.txt")
grade_split = grade.split('\n')
self.assertEqual(grade_split[1], "Wrong:0 ()")
grade = out_grade("Exercise.txt", "Wrong.txt")
grade_split = grade.split('\n')
self.assertEqual(grade_split[0], "Correct:0 ()")
5. test_to_fraction:测试假分数转换为真分数是否正确
def test_to_fraction(self):
fraction = to_fraction("89/55")
self.assertEqual(fraction, "1'34/55")
fraction = to_fraction("10/5")
self.assertEqual(fraction, "2")
fraction = to_fraction("5'10")
self.assertEqual(fraction, ValueError)
6. test_10000_n:测试产生10000道题目
@mock.patch("Myapp.add_parm")
def test_10000_n(self, mock_args):
mock_args.return_value = Namespace(answer_file=None, exercise_file=None, range=5, sum=10000)
main()
exercise_file = open("Exercise.txt", "r", encoding='utf-8')
exercise_lines = exercise_file.readlines()
answer_file = open("Answer.txt", "r", encoding='utf-8')
answer_lines = answer_file.readlines()
exercise_file.close()
answer_file.close()
self.assertEqual(len(exercise_lines), 10000)
self.assertEqual(len(answer_lines), 10000)
测试结果
测试全部执行成功。
覆盖率
小结
- 林楦:在去除重复表达式和添加多个小括号中一开始一直没有特别好的思路,拖了很长时间,通过不断查找资料,才陆陆续续有灵感。关于结对编程,我的搭档一直在帮助我,我基础较为薄弱,他会帮我找到很多有用的资料,帮我想到更简单的实现,我觉得结对编程需要队友间进行多交流沟通,好的合作可以大大提高开发效率。
- 徐嘉炜:在结对编程的过程中,我认为一个非常重要的点就是集合讨论整个项目的需求以及如何实现。结对编程的优点便是两个人加快了编程的效率,但对于代码的提交,要记得提交之前先拉取合并,避免覆盖掉队友的,导致拉慢了进度,队友之间也要经常对接实现进度。