软件工程作业——结对项目

news/2024/11/15 15:43:46/文章来源:https://www.cnblogs.com/guanrui03/p/18428874
这个作业属于哪个课程 22级计科12班
这个作业要求在哪 作业要求
这个作业的目标 实现一个自动生成小学四则运算题目的命令行程序

成员

姓名 学号 GitHub地址
吕宏鸿 3122004446 结对项目
宋观瑞 3122004402 结对项目

1.PSP表格

PSP2.1 预估耗时(分钟) 实际耗时(分钟)
计划 10 5
* 估计这个任务需要多少时间 10 5
开发 1210 1110
* 需求分析 (包括学习新技术) 100 150
* 生成设计文档 30 30
* 设计复审 10 10
* 代码规范 (为目前的开发制定合适的规范) 30 20
* 具体设计 180 200
* 具体编码 500 300
* 代码复审 180 200
* 测试(自我测试,修改代码,提交修改) 180 200
报告 180 100
* 测试报告 90 60
* 计算工作量 60 10
* 事后总结, 并提出过程改进计划 30 30
合计 1400 1215

2.效能分析

性能测试

这里使用PyCharm自带的profile进行性能测试,下图是未修改代码是的耗时,可以看到耗时最长的自建函数是 answer_question(耗时59ms)。通过检查代码发现是因为生成的例子很多,逐个生成耗时长,且每次生成例子都得进行I/O操作将例子写入文件中。

为了减少耗时,这里使用了多线程技术,运行多个线程同时生成例子,且仅仅在最后才将例子写入文件。

优化后耗时7ma,耗时下降了88%,算是一个巨大的提升了。

dad

3.设计实现过程

下面是程序的总体运行流程图。

dada

Question类:

generate_question: 生成一个数学表达式问题,包括随机生成的操作数和运算符。

下面是generate_question方法的调用关系流程图

dada

generate_symbols: 随机生成运算符。

generate_bracket: 随机决定是否添加括号,并确定括号位置。

generate_rational_numbers: 生成操作数,随机决定每个操作数是整数或分数。

validate_rational_numbers: 验证操作数的有效性,确保减法运算不会导致非正数。

generate_string: 根据生成的操作数和运算符,构建数学表达式的字符串形式。
calculate_result: 计算表达式的结果,并确保结果是正数。

file_operations:

generate_single_question: 生成一个数学题目并返回题目及答案。

answer_questions: 利用多线程,生成并解答随机的数学题目,将题目和答案分别保存到文件中。

check_answer: 校对题目和答案文件,检查答案是否正确,并将结果写入文件。

utils:

fraction_to mixed: 将 Fraction 对象转换为带分数的字符串表示形式。

mixed_to_fraction: 将带分数的字符串表示转换为 Fraction 对象。

main:

get_arguments: 解析命令行参数,返回包含参数的命名空间对象。

run_cmd_mode: 运行命令行模式,通过解析参数并生成题目或检查答案。

run_gui_mode: 运行图形化界面模式,通过解析参数并生成题目或检查答案。

4.代码说明

代码说明。展示出项目关键代码,并解释思路与注释说明。(4分)

1.question类

设计用于生成包含随机操作数(整数或分数)、运算符(加、减、乘、除)以及可选括号的数学表达式,并计算其结果。

(1)question方法

生成一个数学表达式问题,包括随机生成的操作数和运算符,以及可选的括号。

点击查看代码
```plaintextdef generate_question(self, max_value, num=4):"""生成一个数学表达式问题,包括随机生成的操作数和运算符。:param max_value: 操作数的最大值。:param num: 操作数的最大数量,默认值为 4。"""self.total = self.ran.randint(2, num)  # 随机生成操作数的数量,最少 2 个self.generate_symbols()  # 生成运算符self.generate_bracket()  # 生成括号self.generate_rational_numbers(max_value)  # 生成操作数self.generate_string()  # 生成表达式的字符串形式self.result = self.calculate_result()  # 计算表达式结果

(2)generate_symbols方法

随机生成与操作数数量相匹配的运算符列表。

点击查看代码
```plaintextdef generate_symbols(self):"""随机生成运算符。根据操作数数量,从操作符列表中随机选择运算符。"""self.symbols = [self.ran.choice(self.operators) for _ in range(self.total - 1)]

(3)generate_bracket方法

根据运算符类型和数量,随机决定是否添加括号并确定其位置。

点击查看代码
```plaintextdef generate_bracket(self):"""随机决定是否添加括号,并确定括号位置。如果操作符数量大于1,且是加法或减法运算,随机添加括号。"""if len(self.symbols) > 1:# 当符号是加法或减法时,有一定几率在符号位置插入括号self.bracket = next((i for i in range(self.total - 1) if self.symbols[i] in ['+', '-'] and self.ran.choice([True, False])),-2)

(4)generate_rational_numbers方法

生成随机整数或分数作为操作数,并确保操作数不会导致非法计算(如减法结果非正)。

点击查看代码
```plaintextdef generate_rational_numbers(self, max_value):"""生成操作数,随机决定每个操作数是整数或分数。:param max_value: 操作数的最大值。"""self.rational_numbers = [# 随机决定操作数是整数还是分数self.ran.randint(1, max_value) if self.ran.choice([True, False])else Fraction(self.ran.randint(1, self.ran.randint(2, max_value) - 1), self.ran.randint(2, max_value))for _ in range(self.total)]self.validate_rational_numbers()  # 验证生成的操作数是否有效

(5)validate_rational_numbers方法

验证操作数的有效性,确保减法运算不会导致非正数结果。

点击查看代码
```plaintextdef validate_rational_numbers(self):"""验证操作数的有效性,确保减法运算不会导致非正数。如果操作数导致结果为负数或零,抛出异常。"""for i in range(1, self.total):# 如果符号是减法,且前后两个操作数相减结果为 0 或负数,则抛出异常if self.symbols[i - 1] == '-' and self.rational_numbers[i - 1] - self.rational_numbers[i] <= 0:raise ValueError  # 抛出异常以重新生成问题

(6)generate_string方法

根据生成的操作数和运算符,构建数学表达式的字符串形式,包括括号。

点击查看代码
```plaintextdef generate_string(self):"""根据生成的操作数和运算符,构建数学表达式的字符串形式。"""string_ver = ""  # 初始化空字符串,用于存储表达式# 遍历操作数和运算符,构建表达式字符串for i in range(self.total - 1):if self.bracket == i:string_ver += '('  # 如果当前索引等于括号位置,添加左括号# 如果当前操作数是分数,将其包裹在括号中if isinstance(self.rational_numbers[i], Fraction):string_ver += '(' + str(self.rational_numbers[i]) + ')'else:string_ver += str(self.rational_numbers[i])  # 否则直接添加整数if self.bracket + 1 == i:string_ver += ')'  # 添加右括号string_ver += self.symbols[i]  # 添加运算符# 添加最后一个操作数if isinstance(self.rational_numbers[-1], Fraction):string_ver += '(' + str(self.rational_numbers[-1]) + ')'else:string_ver += str(self.rational_numbers[-1])# 如果括号位置在最后一个运算符处,添加右括号if self.bracket == i:string_ver += ')'# 将生成的表达式存储为字符串形式self.string_ver = string_ver

(7)calculate_result方法

计算表达式的结果,并确保结果为正数。如果结果为非正数,则抛出异常。

点击查看代码
```plaintextdef calculate_result(self):"""计算表达式的结果,并确保结果是正数。:return: 计算后的表达式结果,使用 Fraction 确保分数表示。"""# 使用 eval 函数计算字符串形式的表达式结果,并将其转换为 Fraction 类型result = Fraction(eval(self.string_ver)).limit_denominator(1024)# 确保结果大于0if result <= 0:raise ValueError  # 如果结果不为正,抛出异常self.result = result  # 存储计算结果return result  # 返回结果

2.question函数

(1)answer_question函数

生成并解答随机的数学题目,将题目和答案分别保存到文件中。

点击查看代码
```plaintext
def answer_questions(r, n, answer_mode=False):"""生成并解答随机的数学题目,将题目和答案分别保存到文件中。:param r: 操作数的最大值:param n: 题目数量:param answer_mode: 是否启用用户答题模式,True 为启用答题模式"""correct_answers = 0  # 用于统计用户正确答题数wrong_answers = 0  # 用于统计用户错误答题数# 计时start_time = time.time()# 清空已有的题目和答案文件with open("file\\Exercise.txt", 'w', encoding="UTF-8"), open("file\\Answer.txt", 'w', encoding="UTF-8"):pass  # 占位操作,目的是清空文件内容# 用于存储生成的题目和答案的列表question_data = []# 使用线程池并行生成题目if n > 5000:with concurrent.futures.ThreadPoolExecutor(max_workers=1024) as executor:futures = [executor.submit(generate_single_question, r, question_num) for question_num in range(1, n + 1)]# 查看当前活跃的线程数量print(f"当前活跃的线程数: {threading.active_count()}")for future in concurrent.futures.as_completed(futures):question_data.append(future.result())else:with concurrent.futures.ThreadPoolExecutor() as executor:futures = [executor.submit(generate_single_question, r, question_num) for question_num in range(1, n + 1)]# 查看当前活跃的线程数量print(f"当前活跃的线程数: {threading.active_count()}")for future in concurrent.futures.as_completed(futures):question_data.append(future.result())# 按题号对生成的题目和答案排序question_data.sort(key=lambda x: x[0])# 将生成的题目和答案写入文件中with open("file\\Exercise.txt", 'a', encoding="UTF-8") as fp1, open("file\\Answer.txt", 'a', encoding="UTF-8") as fp2:for _, question_text, answer_text in question_data:fp1.write(question_text)fp2.write(answer_text)# 如果启用了答题模式if answer_mode:for question_num, question_text, answer_text in question_data:answer = input(f"({question_num}){question_text.strip()} ")# 将用户输入的答案转换为 Fraction 类型answer = mixed_to_fraction(answer)# 解析正确答案correct_answer = mixed_to_fraction(answer_text.split(". ")[1].strip())# 判断用户答案是否正确if answer == correct_answer:print("√")  # 如果正确,打印 √correct_answers += 1  # 正确答题数加 1else:print(f"× | The correct answer is: {fraction_to_mixed(correct_answer)}")wrong_answers += 1  # 错误答题数加 1print(f"正确题目数:{correct_answers}\n错误题目数:{wrong_answers}")else:# 否则,提示生成已完成print("生成已经完成!")# 结束时间end_time = time.time()# 计算执行时间elapsed_time = end_time - start_timeprint(f"耗时: {elapsed_time:.2f} s")

(2)check_answer函数

校对题目和答案文件,检查答案是否正确,并将结果写入文件。

点击查看代码
```plaintext
def check_answer(exercise_file, answer_file):"""校对题目和答案文件,检查答案是否正确,并将结果写入文件。:param exercise_file: 题目文件路径:param answer_file: 答案文件路径"""correct_answers = 0  # 正确答案数量wrong_answers = 0  # 错误答案数量correct_list = []  # 保存正确题目的编号wrong_list = []  # 保存错误题目的编号# 读取题目和答案文件的行数,以便计算总进度with open(exercise_file, 'r', encoding="UTF-8") as fp1, open(answer_file, 'r', encoding="UTF-8") as fp2:total_lines = sum(1 for _ in fp1)  # 计算题目行数fp1.seek(0)  # 重置文件指针到开头fp2.seek(0)  # 重置文件指针到开头counter = 1  # 题目编号计数器exe_line = fp1.readline()  # 读取一行题目ans_line = fp2.readline()  # 读取一行答案while exe_line and ans_line:# 提取出题目中的表达式部分exe_line = exe_line[exe_line.index('. ') + 1:-3]# 提取出答案部分,并将其转换为 Fraction 类型ans_line = ans_line[ans_line.index('. ') + 1:]ans_line = str(mixed_to_fraction(ans_line))# 计算题目表达式的正确结果result_exe = Fraction(eval(exe_line)).limit_denominator(1024)# 计算答案的结果result_ans = Fraction(eval(ans_line)).limit_denominator(1024)# 比较答案是否正确if result_ans == result_exe:correct_answers += 1  # 正确答案数加 1correct_list.append(counter)  # 将题号加入正确列表else:wrong_answers += 1  # 错误答案数加 1wrong_list.append(counter)  # 将题号加入错误列表# 更新进度条print("\r", end="")progress = int((counter / total_lines) * 50)  # 进度条长度为20个字符percentage = (counter / total_lines) * 100  # 计算百分比print("校对进度: {:.2f}%: ".format(percentage), "▋" * progress + " " * (50 - progress), end="")sys.stdout.flush()# 读取下一行题目和答案exe_line = fp1.readline()ans_line = fp2.readline()counter += 1  # 题号加 1# 将结果写入成绩文件with open("file\\Grade.txt", 'w', encoding="UTF-8") as fp:fp.write(f"Correct: {correct_answers} {tuple(correct_list)}\n")  # 写入正确答案信息fp.write(f"Wrong: {wrong_answers} {tuple(wrong_list)}\n")  # 写入错误答案信息print("输出结果已写入file文件夹中的Grade.txt")

3.utils函数

(1)fraction_to_mixed函数

功能是将一个 Fraction 对象转换为带分数的字符串表示形式。

点击查看代码
```plaintext
def fraction_to_mixed(frac: Fraction) -> str:"""将 Fraction 对象转换为带分数的字符串表示形式。:param frac: 需要转换的 Fraction 对象,表示分数。:return: 分数的字符串表示形式,如果是假分数,返回带分数格式,否则返回分数的原始字符串。- 如果分数的分子(numerator)小于或等于分母(denominator),或分母为 1,则直接返回该分数的字符串表示。- 如果是假分数(分子大于分母),则返回带分数的格式:例如,对于 7/3,返回 "2'1/3",即带分数形式:整数部分'分数部分。"""# 如果分子小于等于分母,或分母为1,返回分数原始形式if frac.numerator <= frac.denominator or frac.denominator == 1:return str(frac)  # 直接返回 Fraction 的字符串表示# 对于假分数,返回带分数形式# 整数部分: frac.numerator // frac.denominator,余数部分为 frac - (整数部分)else:return f"{frac.numerator // frac.denominator}'{frac - frac.numerator // frac.denominator}"

(2)mixed_to_fraction函数

这个函数 mixed_to_fraction 的目的是将带分数的字符串表示(如 "2'1/3")或简单的分数字符串(如 "3/4")转换为 Fraction 对象。

点击查看代码
```plaintext
def mixed_to_fraction(string: str) -> Fraction:"""将带分数的字符串表示转换为 Fraction 对象。:param string: 带分数的字符串,例如 "2'1/3" 或简单的 "3/4"。:return: 对应的 Fraction 对象。- 如果字符串包含带分数形式(即包含 "'" 和 "/"),将其分为整数部分和分数部分,并组合成一个 Fraction 对象。- 如果是普通分数,直接将其转换为 Fraction 对象。"""# 如果字符串包含带分数的表示形式(即同时包含 "'" 和 "/")if "'" in string and '/' in string:whole, frac_part = string.split("'")  # 将字符串分为整数部分和分数部分numerator, denominator = map(int, frac_part.split("/"))  # 将分数部分解析为分子和分母# 返回整数部分 + 分数部分组成的 Fraction 对象return int(whole) + Fraction(numerator, denominator)# 如果字符串表示的是简单的分数形式,则直接转换为 Fraction 对象return Fraction(string)

4.main函数

点击查看代码
```plaintext
def get_arguments():"""解析命令行参数,返回包含参数的命名空间对象。参数包括生成题目的数量、最大数值、是否启用答题模式、题目文件路径、答案文件路径等。"""parser = argparse.ArgumentParser(description="Usage Example: python main.py -n 3 -r 10",  # 描述程序用途epilog="若需要使用判题功能,请勿输入-r参数"  # 在帮助信息末尾显示的额外信息)# 添加命令行参数parser.add_argument('-n', type=int, help="生成题目的个数(默认为10)")parser.add_argument('-r', type=int, help="题目中的最大数值")parser.add_argument('-m', action='store_true', help="启用答题模式(默认关闭)")parser.add_argument('-e', type=str, help="题目文件路径")parser.add_argument('-a', type=str, help="答案文件路径")return parser.parse_args()  # 返回解析后的命名空间对象def run_cmd_mode():"""运行命令行模式,通过解析参数并生成题目或检查答案。"""args = get_arguments()  # 获取命令行参数if args.n:  # 如果提供了题目数量参数n = args.nelse:n = 10  # 如果未提供题目数量,则默认生成 10 道题目if args.r:  # 如果提供了最大数值参数r = args.rm = args.m if args.m else False  # 判断是否启用了答题模式,默认关闭answer_questions(r, n, m)  # 调用生成题目的函数elif args.e and args.a:  # 如果提供了题目文件路径和答案文件路径check_answer(args.e, args.a)  # 调用检查答案的函数else:print("Argument '-r' is needed!")  # 如果没有提供必要参数,输出错误信息os.system("pause")  # 暂停程序等待用户输入,防止窗口立即关闭sys.exit()  # 退出程序def run_gui_mode():"""运行图形化界面模式,用户可以通过界面输入生成题目或答案。"""def generate_questions():"""生成题目并将其显示在图形界面的文本框中,同时生成对应的答案。"""try:n = int(entry_n.get())  # 获取用户输入的题目数量r = int(entry_r.get())  # 获取用户输入的最大数值answer_mode = var_answer_mode.get()  # 获取用户是否启用答题模式的选择# 清空文本框内容text_output.delete(1.0, tk.END)# 调用生成题目函数,并根据用户选择的模式生成题目或答案answer_questions(r, n, answer_mode)# 读取生成的题目和答案文件,并显示在文本框中with open("file/Exercise.txt", 'r', encoding="UTF-8") as f:text_output.insert(tk.END, f.read())  # 将生成的题目写入文本框with open("file/Answer.txt", 'r', encoding="UTF-8") as f:text_output.insert(tk.END, f"\n\nAnswers:\n{f.read()}")  # 将生成的答案写入文本框except ValueError:messagebox.showerror("输入错误", "请确保输入的是有效的数字。")  # 如果输入的不是有效数字,则弹出错误消息框# 创建主窗口root = tk.Tk()root.title("四则运算生成器")  # 设置窗口标题root.geometry("600x400")  # 设置窗口尺寸# 题目数量输入框label_n = tk.Label(root, text="题目数量 (n):")label_n.pack()entry_n = tk.Entry(root)entry_n.pack()# 最大数值输入框label_r = tk.Label(root, text="最大数值 (r):")label_r.pack()entry_r = tk.Entry(root)entry_r.pack()# 答题模式选项(复选框)var_answer_mode = tk.IntVar()  # 用于存储复选框的值check_answer_mode = tk.Checkbutton(root, text="启用答题模式", variable=var_answer_mode)  # 答题模式复选框check_answer_mode.pack()# 生成题目按钮button_generate = tk.Button(root, text="生成题目", command=generate_questions)  # 点击按钮后调用生成题目的函数button_generate.pack()# 输出区域(用于显示生成的题目和答案)text_output = tk.Text(root, height=15, width=70)text_output.pack()# 启动图形化界面的主循环root.mainloop()if __name__ == '__main__':# 判断是否传入命令行参数,如果有参数,则运行命令行模式;否则运行图形化界面if len(sys.argv) > 1:run_cmd_mode()  # 运行命令行模式else:run_gui_mode()  # 启动图形化界面

5.运行测试

utils测试

测试包括函数能否将真分数、假分数、整数和字符串相互转换,以及函数在接受到无效输入时的行为

点击查看代码
```plaintext
class TestFractionConversion(unittest.TestCase):def test_fraction_to_mixed_proper_fraction(self):# 测试真分数frac = Fraction(1, 3)expected = "1/3"result = fraction_to_mixed(frac)self.assertEqual(result, expected, msg="Failed to convert proper fraction to string")def test_fraction_to_mixed_improper_fraction(self):# 测试假分数frac = Fraction(7, 3)expected = "2'1/3"result = fraction_to_mixed(frac)self.assertEqual(result, expected, msg="Failed to convert improper fraction to mixed string")def test_fraction_to_mixed_whole_number(self):# 测试整数(可以视为分母为1的分数)frac = Fraction(5, 1)expected = "5"result = fraction_to_mixed(frac)self.assertEqual(result, expected, msg="Failed to convert whole number fraction to string")def test_mixed_to_fraction_mixed_number(self):# 测试带分数mixed_str = "2'1/3"expected = Fraction(7, 3)result = mixed_to_fraction(mixed_str)self.assertEqual(result, expected, msg="Failed to convert mixed number string to Fraction")def test_mixed_to_fraction_simple_fraction(self):# 测试简单分数simple_str = "3/4"expected = Fraction(3, 4)result = mixed_to_fraction(simple_str)self.assertEqual(result, expected, msg="Failed to convert simple fraction string to Fraction")def test_mixed_to_fraction_invalid_input(self):# 测试无效输入(不包含 '/')invalid_str = "2'1"with self.assertRaises(ValueError, msg="Expected ValueError for invalid input"):mixed_to_fraction(invalid_str)def test_mixed_to_fraction_integer_input(self):# 测试整数输入(虽然不是带分数,但函数应该能够处理)integer_str = "5"expected = Fraction(5, 1)result = mixed_to_fraction(integer_str)self.assertEqual(result, expected, msg="Failed to convert integer string to Fraction")

file_operations测试

TestAnswerQuestions 类主要用于测试函数是否能生成少量题目(以10为例)、生成大量题目(5000为例)、以及测试生成0个题目的边界情况

点击查看代码
```plaintext
class TestAnswerQuestions(unittest.TestCase):def test_basic_function(self):# 测试生成10个题目r = 10  # 操作数的最大值n = 10  # 题目数量answer_questions(r, n)# 这里可以添加检查文件内容的代码来验证是否正确生成了题目def test_large_number_of_questions(self):# 测试生成大量题目r = 100n = 5000answer_questions(r, n)# 验证文件内容或性能(可能需要单独的性能测试工具)def test_zero_questions(self):# 测试生成0个题目r = 10n = 0answer_questions(r, n)

TestCheckAnswer 类用于测试 check_answer 函数,该函数负责比较题目文件和答案文件,并生成成绩文件。

点击查看代码
```plaintext
class TestCheckAnswer(unittest.TestCase):def setUp(self):ExerciseTestfile_path = "E:\python project\PairItem\Four_Basic_Operations\\file\ExerciseTest.txt"AnswerTestfile_path = "E:\python project\PairItem\Four_Basic_Operations\\file\AnswerTest.txt"grade_file_path = "E:\python project\PairItem\Four_Basic_Operations\\file\Grade.txt"# 使用'w'模式打开文件,这将清空文件内容(如果文件已存在)# 或者创建一个新文件(如果文件不存在)with open(grade_file_path, 'w') as grade_file:passwith open(ExerciseTestfile_path, 'r', encoding='utf-8') as file:self.ExerciseTestfile_content = file.readlines()passwith open(AnswerTestfile_path, 'r', encoding='utf-8') as file:self.AnswerTestfile_content = file.readlines()passdef test_basic_function(self):# 假设已经有两个文件:Exercise.txt 和 Answer.txt,包含正确和错误的答案exercise_file = self.ExerciseTestfile_contentanswer_file = self.AnswerTestfile_contentcheck_answer("E:\python project\PairItem\Four_Basic_Operations\\file\ExerciseTest.txt", "E:\python project\PairItem\Four_Basic_Operations\\file\AnswerTest.txt")# 验证Grade.txt文件的内容是否正确def test_empty_files(self):# 测试空文件with open("E:\python project\PairItem\Four_Basic_Operations\\file\Exercise_emptyTest.txt", 'w') as fp:passwith open("E:\python project\PairItem\Four_Basic_Operations\\file\Answer_emptyTest.txt", 'w') as fp:passcheck_answer("E:\python project\PairItem\Four_Basic_Operations\\file\Exercise_emptyTest.txt", "E:\python project\PairItem\Four_Basic_Operations\\file\Answer_emptyTest.txt")# 验证Grade.txt文件的内容(应该显示没有题目)Four_Basic_Operations

结果验证
题目文件
答案文件
由上面的测试题目和测试答案对比,可知这个测试用例成功

Question测试

TestQuestion 类包含了一系列用于测试 Question 类的测试用例。这些测试用例覆盖了生成问题、符号、有理数、字符串表示、计算结果以及验证有理数等方面的功能。

点击查看代码
```plaintext
class TestQuestion(unittest.TestCase):@patch('question.Random')  # 模拟 Random 类def test_generate_question(self, mock_random):# 初始化 Mock 随机数生成器的行为mock_random.return_value = Random(42)  # 让随机数生成器有可预期的输出question = Question()question.generate_question(max_value=10, num=3)# 检查生成的运算符self.assertEqual(len(question.symbols), question.total - 1)# 检查操作数的个数是否正确self.assertEqual(len(question.rational_numbers), question.total)# 确保结果是 Fraction 类型并且结果为正数self.assertTrue(isinstance(question.result, Fraction))self.assertGreater(question.result, 0)def test_generate_symbols(self):question = Question(total=4)question.generate_symbols()# 确保 symbols 数量和 total - 1 是一致的self.assertEqual(len(question.symbols), 3)for symbol in question.symbols:self.assertIn(symbol, question.operators)@patch('random.Random.choice')def test_generate_bracket_no_symbols(self, mock_choice):# 测试当 symbols 列表长度小于等于1时,不应生成括号question = Question(total=2, symbols=[])question.generate_bracket()self.assertEqual(question.bracket, -2)  # 确保没有生成括号@patch('random.Random.choice')def test_generate_bracket_with_addition(self, mock_choice):# 测试当 symbols 中包含加法运算时,括号生成的位置mock_choice.return_value = True  # 强制使得 choice 返回 True,以便生成括号question = Question(total=3, symbols=['+', '*'])question.generate_bracket()# 确保括号生成在加法符号的位置self.assertEqual(question.bracket, 0)@patch('random.Random.choice')def test_generate_bracket_with_subtraction(self, mock_choice):# 测试当 symbols 中包含减法运算时,括号生成的位置mock_choice.return_value = True  # 强制使得 choice 返回 True,以便生成括号question = Question(total=4, symbols=['*', '-'])question.generate_bracket()# 确保括号生成在减法符号的位置self.assertEqual(question.bracket, 1)@patch('random.Random.choice')def test_no_bracket_generated(self, mock_choice):# 测试当 Random.choice 返回 False 时,不应生成括号mock_choice.return_value = False  # 强制 choice 返回 Falsequestion = Question(total=4, symbols=['+', '-'])question.generate_bracket()self.assertEqual(question.bracket, -2)  # 确保没有生成括号def test_generate_rational_numbers(self):question = Question(total=3)try:question.generate_symbols()  # 生成符号question.generate_rational_numbers(max_value=9)# 检查生成的操作数数量self.assertEqual(len(question.rational_numbers), 3)for num in question.rational_numbers:# 检查操作数是否为整数或 Fraction 类型self.assertTrue(isinstance(num, int) or isinstance(num, Fraction))except ValueError as e:self.fail(f"生成的操作数无效: {str(e)}")def test_generate_string(self):question = Question(total=3, rational_numbers=[1, Fraction(1, 2), 3], symbols=['+', '*'])question.generate_string()expected_string = "1+(1/2)*3"self.assertEqual(question.string_ver, expected_string)def test_calculate_result(self):question = Question(total=3, rational_numbers=[1, Fraction(1, 2), 3], symbols=['+', '*'])question.generate_string()result = question.calculate_result()# 检查计算结果self.assertEqual(result, Fraction(5, 2))def test_validate_rational_numbers(self):question = Question(total=3, rational_numbers=[5, 3, 2], symbols=['-', '+'])with self.assertRaises(ValueError):question.validate_rational_numbers()  # 应该抛出 ValueError,因为 5 - 3 = 2, 3 - 2 = 1 是有效的

main测试

点击查看代码
```plaintext
class TestMainFunctions(unittest.TestCase):@patch('main_2.answer_questions')  # Mock answer_questions@patch('main_2.check_answer')  # Mock check_answerdef test_run_cmd_mode_generate_questions(self, mock_check_answer, mock_answer_questions):# 模拟命令行参数test_args = ['main.py', '-n', '5', '-r', '10']with patch.object(sys, 'argv', test_args):run_cmd_mode()mock_answer_questions.assert_called_once_with(10, 5, False)@patch('main_2.answer_questions')@patch('main_2.check_answer')def test_run_cmd_mode_check_answers(self, mock_check_answer, mock_answer_questions):# 模拟命令行参数test_args = ['main.py', '-e', 'questions.txt', '-a', 'answers.txt']with patch.object(sys, 'argv', test_args):run_cmd_mode()mock_check_answer.assert_called_once_with('questions.txt', 'answers.txt')@patch('builtins.print')@patch('os.system')def test_run_cmd_mode_missing_argument(self, mock_os_system, mock_print):# 模拟命令行参数缺失test_args = ['main.py']with patch.object(sys, 'argv', test_args):with self.assertRaises(SystemExit):  # 期望程序退出run_cmd_mode()mock_print.assert_called_once_with("Argument '-r' is needed!")  # 确保输出了错误信息mock_os_system.assert_called_once_with("pause")  # 确保调用了暂停def test_get_arguments(self):# 测试命令行参数解析test_args = ['main.py', '-n', '5', '-r', '10']with patch.object(sys, 'argv', test_args):args = get_arguments()self.assertEqual(args.n, 5)self.assertEqual(args.r, 10)self.assertFalse(args.m)  # 默认是 Falsedef test_get_arguments_invalid_input(self):# 测试命令行参数解析无效输入test_args = ['main.py', '-n', 'x', '-r', 'x']with patch.object(sys, 'argv', test_args):with self.assertRaises(SystemExit):  # argparse 会调用 sys.exit()get_arguments()

6.项目小结

本次作业第一次接触到双人合作的项目,做起来实际上和单人项目的感觉差不多,每个人都需要了解到整个程序时怎么运行的,不单单是完成各自的模块。但是对于结对项目,因为具体分工的远古,在具体的编码过程工作量相对减少了很多,这也是比较单人项目完成的效率更高。对于结对项目,我认为能较好的锻炼我们在队伍中的交流沟通能力,具体分工也要因人而异,两个的水平不一,擅长的领域也不尽相同,需要良好的沟通去发挥各自的优势,并且在合作的过程中两个人相互学习,两人的能力都能得到提升。但是由于第一次合作,只是较为基础的完成了任务,仍需要更多的交流和学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/803086.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

9月10日循环条件的结束

在测试编程中涉及到输入错误要重新返回UI界面,但是我写的总是输入不管是对还是错都会直接结束程序,完全不符合要求,经过整理思路,查询代码结构,此处应该设计为双层循环外部为while,内部为witch case语句,当输入为1时执行case==1;经应该是执行生产计划类然后跳出witch条…

IDEA更改远程git仓库地址

前言 我们在使用IDEA开发时,一般会配置好对应的git仓库,这样就比较容易对代码进行控制以及协同开发。但有时候,我们远程的仓库地址由于这样那样的原因,需要迁移(这在爱折腾的企业是常有的事情)。那么,我们该如何在IDEA中更新远程仓库地址呢? 如何设置 首先,我们点击上…

vue3开发中易遗漏的常见知识点

组件样式的特性 Scoped CSS之局部样式的泄露 示例(vue3): 父组件: <template><h4>App Title</h4><hello-world></hello-world> </template> <script> import HelloWorld from ./HelloWorld.vue;export default {name: App,compo…

PasteForm最佳CRUD实践,实际案例PasteTemplate详解(一)

本文将介绍soft.pastecode.cn出品的PasteForm,PasteForm是贴代码使用Dto思想实现的CRUD的一个组件,或者说输出一个思想! 为啥我觉得是最佳的CRUD呢?先结合你的实际项目解答下以下问题: 1.如果有一个系统,有100个表,你的管理端需要多少页面?别和我说100个表很多,需求复…

RTE大会报名丨 重塑语音交互:音频技术和 Voice AI,RTE2024 技术专场第一弹!

Voice AI 实现 human-like 的最后一步是什么?AI 视频爆炸增长,新一代编解码技术将面临何种挑战?当大模型进化到实时多模态,又将诞生什么样的新场景和玩法?所有 AI Infra 都在探寻规格和性能的最佳平衡,如何构建高可用的云边端协同架构?AI 加持下,空间计算和新硬件也迎来…

彻底搞懂回溯算法

1.回溯算法的核心思想 回溯算法的核心思想是:尝试+记录+回退。 先尝试一种选项,在选择该选项的前提下继续寻解,如果最后寻解成功,则记录这个解,否则不用记录,然后再回退到选择该选项前的状态,改为尝试其它选项再继续寻解,判断其它选项是不是解。 2.回溯算法的关键点 回溯…

9.23 ~ 9.29

集训9.23 集训第一天。 早晨因为太多人没拿早读资料被老登 D 了。 不是哥们你不早说 现在我上哪给你找资料去 😅 上午模拟赛。 发现 T1 的图挂了,于是看形式化题意;初始有一张 \(n\) 个点的完全图,接着删除 \(m\) 条边。 询问有多少长度为 \(13\) 的序列 \(p_1,...,p_{13}…

CSP 集训记录

用来整理模拟赛等9.23 csp-3【noip23 ZR二十连测 DAY10】 保龄. A.奇观 狗市题目描述。不是这题意太大歧义了吧,我讨厌的第二种出题人——题意描述相当不清。CTH:13 座城市又不代表是 13 座不同的城市。直接看形式化题目的话(如果能看懂要干什么)那这题确实不难。 解: 容易…

判断系统大小端字节序的方法

1、字节序 1.1、大端字节序(big-endian) 数据低位存储在高地址位,数据高位存储在低地址位。 假设定义一个变量并赋予初值: int a = 0x12345678; 对于这个整型数据,一共有四个字节,假设为其分配的地址空间为0x1001~0x1004,则从低位到高位,每个字节依次是:12、34、56、78。…

第二十四讲:MySQL是怎么保证高可用的?

为了让各位更好的了解文章,我归纳了下面几点最重要的: 1、MySQL 高可用系统的可用性,是依赖于主备延迟的。延迟的时间越小,主库故障的时候,服务恢复需要的时间就越短,可用性就越高。 2、主备延迟原因:备库用的机子不行(IOPS是和主库相同的,不要轻视备库)、备库压力太…

redis内容记录

redis的基本数据类型String:是最基本的数据类型,它可以存储任何二进制安全的数据。 不仅能存放文本数据,还能保存图片、音频、视频、压缩文件等二进制数据。它们通常用于缓存。 Hash:哈希类型,其中键值对中的值本身又是一个键值对结构,hash 特别适合用于存储对象。 List:…

人工智能教育技术学第四周

1.用亿图图示制作黄山奇石语文课文的思维导图2.CAJViewer9.2(CAJ全文浏览器)是中国知网的专用全文格式阅读器,CAJ浏览器支持中国期刊网的CAJ、PDF、KDH等多种格式文件阅读。并且它的打印效果与原版的效果一致。可实现页面设置、浏览页面、查找文字、切换显示语言、文本摘录、…