四则运算题目生成器
本部分着重研究功能需求中第一阶段第二阶段完成的编码内容的单元测试部分,还有性能测试部分,后续将会有Django部分的单元测试和集成测试部分
对项目之前的完成情况感兴趣,请移步第一阶段和第二阶段的实现过程
单元测试部分
单元测试是软件生命周期中很重要的一个部分,一方面要对每个模块的输入输出进行测试,尽量找出程序中的错误,而且能避免开发过程中的错误不断积累,导致软件工程项目的最终失败,所以在此对第一阶段和第二阶段实现的编码过程进行了测试
单元测试用例
单元测试部分由我和我的队友合作完成,她的单元测试部分链接ohohoho单元测试链接,如果对她的单元测试感兴趣请移步上述链接
测试用例
import unittest
from OriginRequest import BiTree, QuestGenerator
from decimal import Decimal
class TestFun(unittest.TestCase):
times = 0
@classmethod
def setUpClass(cls):
print('setUpclass')
cls.question = QuestGenerator()
def setUp(self):
# 每个测试用例执行之前都会执行该方法
TestFun.times += 1
print('setUp', TestFun.times)
def tearDown(self):
# 每个测试用例执行之后都会执行该方法
TestFun.times += 1
print('tearDown', TestFun.times)
def Test5(self):
k = self.question.changePowOp('(5+5)^2', True)
self.assertEqual(k, '(5+5)^2', 'not_pass')
def Test6(self):
k = self.question.round_up(7.565)
self.assertEqual(k, Decimal('7.57'))
self.assertEqual(k, Decimal('7.56'), 'not_pass')
def Test7(self):
k = self.question.round_up(7.575)
self.assertEqual(k, Decimal('7.58'))
self.assertEqual(k, Decimal('7.57'), 'not_pass')
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTest(TestFun('Test5'))
suite.addTest(TestFun('Test6'))
suite.addTest(TestFun('Test7'))
runner = unittest.TextTestRunner()
runner.run(suite)
我测试的用例不完整,另外的一部分是队友进行的测试,我进行了7次测试,发现了代码中的一个问题,首先说一说测试用例的选择,前四个都是对generate进行的测试,是黑盒测试,用的是边界值法进行的测试,所以在此没有显示,关于单元测试部分,Test5是对changeoperation函数进行的测试,剩下两个测试用例是对四舍五入的函数进行测试
在测试中发现的问题有四舍五入按照以前的方法没有成功,还是科学运算四舍六入的进制,遂修改四舍五入函数,使用的decimal而不是roundup,decimal设置参数进制为’ROUND_HALF_UP’,修改了这个bug,重新测试发现结果符合预期输出
补充单元测试用例
以下用例是队友补充的,可能和上面的测试用例存在重复
import unittest
from solve import Solvable
from OriginRequest import BiTree, QuestGenerator
from decimal import Decimal
from fractions import Fraction
class MyclassTest(unittest.TestCase):
def setUp(self) -> None:
self.question = QuestGenerator()
self.solution = Solvable()
self.tree = BiTree()
def tearDown(self) -> None:
pass
def test_changepowop(self):
k = self.question.changepowop('(5+5)^2', True)
self.assertEqual(k, '(5+5)**2', "didn't switch pow operator")
print("The result of changing pow operator")
print(k)
def test_roundup1(self):
k = self.question.round_up(10.822)
self.assertEqual(k, Decimal('10.820'))
print("result of rounding up 10.822")
print(k)
def test_roundup2(self):
k = self.question.round_up(7.565)
self.assertEqual(k, Decimal('7.57'))
print("result of rounding up 7.565")
print(k)
def test_operadd(self):
k = self.tree.getoperorder('+')
self.assertEqual(k, 0)
print("+ prior:")
print(k)
def test_opersub(self):
k = self.tree.getoperorder('-')
self.assertEqual(k, 0)
print("- prior:")
print(k)
def test_opermul(self):
k = self.tree.getoperorder('*')
self.assertEqual(k, 2)
print("* prior:")
print(k)
def test_operdiv(self):
k = self.tree.getoperorder('/')
self.assertEqual(k, 3)
print("/ prior:")
print(k)
def test_operpow(self):
k = self.tree.getoperorder('^')
self.assertEqual(k, 4)
print("^ prior:")
print(k)
def test_add1(self):
k = self.solution.solve(2, 3, '+')
self.assertEqual(k, 5)
print("result of 2 add 3:")
print(k)
def test_add2(self):
k = self.solution.solve(3 / 5, 11 / 7, '+')
self.assertEqual(k, 76 / 35)
print("result of 3/5 add 11/7:")
print(k)
def test_add3(self):
k = self.solution.solve(6.66, 99.458, '+')
self.assertEqual(k, 106.118)
print("result of 6.66 add 99.458:")
print(k)
def test_sub1(self):
k = self.solution.solve(10.87, 6.56, '-')
self.assertEqual(k, -4.31)
print("result of 6.56 sub 10.87:")
print(k)
def test_sub2(self):
k = self.solution.solve(15 / 7, 9 / 13, '-')
self.assertEqual(k, -132 / 91)
print("result of 9/13 - 15/7:")
print(k)
def test_mul1(self):
k = self.solution.solve(3, 4, '*')
self.assertEqual(k, 12)
print("result of 4 mul 3:")
print(k)
def test_mul2(self):
k = self.solution.solve(-3, 4, '*')
self.assertEqual(k, -12)
print("result of 4 mul -3:")
print(k)
def test_mul3(self):
k = self.solution.solve(11 / 3, 21 / 10, '*')
self.assertEqual(k, 231 / 30)
print("result of 21/10 mul 11/3")
print(k)
def test_mul4(self):
k = self.solution.solve(1.68, 99.135, '*')
self.assertEqual(k, 166.5468)
print("result of 99.135 mul 1.68")
print(k)
def test_div1(self):
k = self.solution.solve(4, 5, '/')
self.assertEqual(k, 1.25)
print("result of 5 div 4:")
print(k)
def test_div2(self):
k = self.solution.solve(6, 7, '/')
self.assertEqual(k, 7 / 6)
print("result of 7 div 6:")
print(k)
def test_div3(self):
k = self.solution.solve(1.26, 3.775, '/')
self.assertEqual(round(k, 3), 2.996)
print("result of 3.775 div 1.26:")
print(k)
def test_div4(self):
k = self.solution.solve(13 / 5, 7 / 9, '/')
self.assertEqual(k, 35 / 117)
print("result of 7/9 div 13/5:")
print(k)
def test_div5(self):
k = self.solution.solve(3, -4, '/')
self.assertEqual(k, -4 / 3)
print("result of -4 div 3:")
print(k)
def test_pow(self):
k = self.solution.solve(3, 2, '^')
self.assertEqual(k, 8)
print("result of 2 pow 3")
print(k)
def test_calculator1(self):
k = self.solution.calculator("(6-3)/4")
self.assertEqual(k, 0.75)
def test_calculator2(self):
k = self.solution.calculator("7")
self.assertEqual(k, 7)
def test_calculator3(self):
k = self.solution.calculator("3-8")
self.assertEqual(k, -5)
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTest(MyclassTest('test_ques1'))
suite.addTest(MyclassTest('test_ques2'))
suite.addTest(MyclassTest('test_ques3'))
suite.addTest(MyclassTest('test_ques4'))
suite.addTest(MyclassTest('test_ques5'))
suite.addTest(MyclassTest('test_ques6'))
suite.addTest(MyclassTest('test_changepowop'))
suite.addTest(MyclassTest('test_roundup1'))
suite.addTest(MyclassTest('test_roundup2'))
suite.addTest(MyclassTest('test_operadd'))
suite.addTest(MyclassTest('test_opersub'))
suite.addTest(MyclassTest('test_opermul'))
suite.addTest(MyclassTest('test_operdiv'))
suite.addTest(MyclassTest('test_operpow'))
suite.addTest(MyclassTest('test_add1'))
suite.addTest(MyclassTest('test_add2'))
suite.addTest(MyclassTest('test_add3'))
suite.addTest(MyclassTest('test_sub1'))
suite.addTest(MyclassTest('test_sub2'))
suite.addTest(MyclassTest('test_mul1'))
suite.addTest(MyclassTest('test_mul2'))
suite.addTest(MyclassTest('test_mul3'))
suite.addTest(MyclassTest('test_mul4'))
suite.addTest(MyclassTest('test_div1'))
suite.addTest(MyclassTest('test_div2'))
suite.addTest(MyclassTest('test_div3'))
suite.addTest(MyclassTest('test_div4'))
suite.addTest(MyclassTest('test_div5'))
suite.addTest(MyclassTest('test_pow'))
suite.addTest(MyclassTest('test_calculator1'))
suite.addTest(MyclassTest('test_calculator2'))
suite.addTest(MyclassTest('test_calculator3'))
runner = unittest.TextTestRunner()
runner.run(suite)
这里测试了输入为非法的情况
单元测试覆盖率
单元测试的覆盖率使用coverage模块进行的测试,测试结果如下:
可以看到对于solve和test基本实现了100%覆盖,在OriginRequest中有数段代码没有覆盖到,集中于48-70行,那部分代码是自己写的堆栈类,没有使用,也没有进行测试,后面的代码集中于判断重复、返回幂次符号不同的字符串上
修改后的测试用例
针对上述solve类没有测试完全的情况,对测试用例进行了修改,修改后增添或修改的用例如下
def test_calculator3(self):
k = self.solution.calculator("3-18")
self.assertEqual(k, -15)
def test_pow(self):
k = self.solution.solve(0.5, 4, '^')
self.assertEqual(k, 2)
print("result of 4^(1/2):")
print(k)
def test_pow2(self):
k = self.solution.solve(Fraction(1, 3), 4, '^')
self.assertEqual(k, 'not solvable')
print("result of 3^4:")
print(k)
上述用例分别对28行的两种情况和49行进行了测试,重新测试测试覆盖率,覆盖率结果如下
可以看到经过修改后测试的覆盖率solve类已经达到了100%,接下来要对OriginRequest的用例进行修改,以求达到尽可能高的测试覆盖率,消除90行之后的代码未覆盖的情况
最后经过我们的努力修改了部分测试用例成功提升测试覆盖率至99%,最后没有覆盖的部分是交换左右子树
测试结果如下:
最终在我们的不懈努力下,测试覆盖率已经达到了100%
集成测试
集成测试直接使用TestPer进行的测试
将OriginRequest视为黑盒生成进行测试,测试的结果如下
def Test1(self):
self.question.generate(5, 5, True, True, True, True, 9)
def Test2(self):
self.question.generate(-1, 5, True, True, True, True, 9)
def Test3(self):
self.question.generate(5, -1, True, True, True, True, 9)
def Test4(self):
self.question.generate(5, 5, True, True, True, True, -1)
这四个用例都是对generate模块和它的子系统进行的边界值测试,可以看到第一个是合法输入,剩下的全都是非法输入,测试的结果显示了集成测试的结果,还有边界值的测试用例
def test_ques1(self):
ret = self.question.generate(10, 5, 0, 0, 0, 9)
print("generate questions")
print(ret)
def test_ques2(self):
ret = self.question.generate(10, 5, 1, 0, 0, 9)
print("generate questions")
print(ret)
def test_ques3(self):
ret = self.question.generate(10, 5, 0, 1, 0, 9)
print("generate questions")
print(ret)
def test_ques4(self):
ret = self.question.generate(10, 5, 0, 0, 1, 9)
print("generate questions")
print(ret)
def test_ques5(self):
ret = self.question.generate(5, 8, 0, 0, 0, 15)
print("generate questions")
print(ret)
def test_ques6(self):
ret = self.question.generate(1000, 5, 1, 1, 1, 9)
print("generate questions")
print(ret)
这一部分的集成测试是对边界值进行分析,是合法输入的情况,上面的边界值中是一个正常输入和非法输入的情况,可以看到经过这样的测试对模块的功能进行了验证
测试中发现的问题
经过了单元测试和集成测试,我们发现代码中存在3处缺陷
第一处是四舍五入功能缺陷,对代码进行了修改,第二处是Calculator计算缺陷,某些分支无法覆盖到,第三处是判断pow后结果的缺陷,根据以上检查出的缺陷,对代码进行了修改,并且重新进行了测试,确保修改后的代码错误率低
在测试达到100%的过程中,发现format_expression中的self.val设计错误,应该是self.this_level,进行了修改和更正并且所有在测试过程中发现的bug的代码都已上传至代码仓库
单元测试最后所用用例如下
import unittest
from solve import Solvable
from OriginRequest import BiTree, QuestGenerator
from decimal import Decimal
from fractions import Fraction
class MyclassTest(unittest.TestCase):
def setUp(self) -> None:
self.question = QuestGenerator()
self.solution = Solvable()
self.tree = BiTree()
def tearDown(self) -> None:
pass
def test_ques1(self):
ret = self.question.generate(10, 5, 0, 0, 0, 9)
print("generate questions")
print(ret)
def test_ques7(self):
ret = self.question.generate(100, 1, 0, 0, 15)
print("generate questions")
def test_ques2(self):
ret = self.question.generate(10, 5, 1, 0, 0, 9)
print("generate questions")
print(ret)
def test_ques3(self):
ret = self.question.generate(10, 5, 0, 1, 0, 9)
print("generate questions")
print(ret)
def test_ques4(self):
ret = self.question.generate(10, 5, 0, 0, 1, 9)
print("generate questions")
print(ret)
def test_ques5(self):
ret = self.question.generate(5, 8, 0, 0, 0, 15)
print("generate questions")
print(ret)
def test_ques6(self):
ret = self.question.generate(1000, 5, 1, 1, 1, 9)
print("generate questions")
print(ret)
def test_changepowop(self):
k = self.question.changepowop('(5+5)^2', True)
self.assertEqual(k, '(5+5)**2', "didn't switch pow operator")
print("The result of changing pow operator")
print(k)
def test_roundup1(self):
k = self.question.round_up(10.822)
self.assertEqual(k, Decimal('10.820'))
print("result of rounding up 10.822")
print(k)
def test_roundup2(self):
k = self.question.round_up(7.565)
self.assertEqual(k, Decimal('7.57'))
print("result of rounding up 7.565")
print(k)
def test_operadd(self):
k = self.tree.getoperorder('+')
self.assertEqual(k, 0)
print("+ prior:")
print(k)
def test_opersub(self):
k = self.tree.getoperorder('-')
self.assertEqual(k, 0)
print("- prior:")
print(k)
def test_opermul(self):
k = self.tree.getoperorder('*')
self.assertEqual(k, 2)
print("* prior:")
print(k)
def test_operdiv(self):
k = self.tree.getoperorder('/')
self.assertEqual(k, 3)
print("/ prior:")
print(k)
def test_operpow(self):
k = self.tree.getoperorder('^')
self.assertEqual(k, 4)
print("^ prior:")
print(k)
def test_add1(self):
k = self.solution.solve(2, 3, '+')
self.assertEqual(k, 5)
print("result of 2 add 3:")
print(k)
def test_add2(self):
k = self.solution.solve(3 / 5, 11 / 7, '+')
self.assertEqual(k, 76 / 35)
print("result of 3/5 add 11/7:")
print(k)
def test_add3(self):
k = self.solution.solve(6.66, 99.458, '+')
self.assertEqual(k, 106.118)
print("result of 6.66 add 99.458:")
print(k)
def test_sub1(self):
k = self.solution.solve(10.87, 6.56, '-')
self.assertEqual(k, -4.31)
print("result of 6.56 sub 10.87:")
print(k)
def test_sub2(self):
k = self.solution.solve(15 / 7, 9 / 13, '-')
self.assertEqual(k, -132 / 91)
print("result of 9/13 - 15/7:")
print(k)
def test_mul1(self):
k = self.solution.solve(3, 4, '*')
self.assertEqual(k, 12)
print("result of 4 mul 3:")
print(k)
def test_mul2(self):
k = self.solution.solve(-3, 4, '*')
self.assertEqual(k, -12)
print("result of 4 mul -3:")
print(k)
def test_mul3(self):
k = self.solution.solve(11 / 3, 21 / 10, '*')
self.assertEqual(k, 231 / 30)
print("result of 21/10 mul 11/3")
print(k)
def test_mul4(self):
k = self.solution.solve(1.68, 99.135, '*')
self.assertEqual(k, 166.5468)
print("result of 99.135 mul 1.68")
print(k)
def test_div1(self):
k = self.solution.solve(4, 5, '/')
self.assertEqual(k, 1.25)
print("result of 5 div 4:")
print(k)
def test_div2(self):
k = self.solution.solve(6, 7, '/')
self.assertEqual(k, 7 / 6)
print("result of 7 div 6:")
print(k)
def test_div3(self):
k = self.solution.solve(1.26, 3.775, '/')
self.assertEqual(round(k, 3), 2.996)
print("result of 3.775 div 1.26:")
print(k)
def test_div4(self):
k = self.solution.solve(13 / 5, 7 / 9, '/')
self.assertEqual(k, 35 / 117)
print("result of 7/9 div 13/5:")
print(k)
def test_div5(self):
k = self.solution.solve(3, -4, '/')
self.assertEqual(k, -4 / 3)
print("result of -4 div 3:")
print(k)
def test_pow(self):
k = self.solution.solve(0.5, 4, '^')
self.assertEqual(k, 2)
print("result of 4^(1/2):")
print(k)
def test_pow2(self):
k = self.solution.solve(Fraction(1, 3), 4, '^')
self.assertEqual(k, 'not solvable')
print("result of 3^4:")
print(k)
def test_calculator1(self):
k = self.solution.calculator("(6-3)/4")
self.assertEqual(k, 0.75)
def test_calculator2(self):
k = self.solution.calculator("7")
self.assertEqual(k, 7)
def test_calculator3(self):
k = self.solution.calculator("3-18")
self.assertEqual(k, -15)
def test_changepowop2(self):
k = self.question.changepowop('5^2', False)
self.assertEqual(k, '5^2')
def test_108_109(self):
node1 = BiTree(0, 1)
node2 = BiTree(0, 2)
node3 = BiTree(1, 45)
node3.set_rchild(node2)
node3.set_lchild(node1)
self.question.format_expression(node3)
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTest(MyclassTest('test_ques1'))
suite.addTest(MyclassTest('test_ques2'))
suite.addTest(MyclassTest('test_ques3'))
suite.addTest(MyclassTest('test_ques4'))
suite.addTest(MyclassTest('test_ques5'))
suite.addTest(MyclassTest('test_ques6'))
suite.addTest(MyclassTest('test_ques7'))
suite.addTest(MyclassTest('test_changepowop'))
suite.addTest(MyclassTest('test_changepowop2'))
suite.addTest(MyclassTest('test_roundup1'))
suite.addTest(MyclassTest('test_roundup2'))
suite.addTest(MyclassTest('test_operadd'))
suite.addTest(MyclassTest('test_opersub'))
suite.addTest(MyclassTest('test_opermul'))
suite.addTest(MyclassTest('test_operdiv'))
suite.addTest(MyclassTest('test_operpow'))
suite.addTest(MyclassTest('test_add1'))
suite.addTest(MyclassTest('test_add2'))
suite.addTest(MyclassTest('test_add3'))
suite.addTest(MyclassTest('test_sub1'))
suite.addTest(MyclassTest('test_sub2'))
suite.addTest(MyclassTest('test_mul1'))
suite.addTest(MyclassTest('test_mul2'))
suite.addTest(MyclassTest('test_mul3'))
suite.addTest(MyclassTest('test_mul4'))
suite.addTest(MyclassTest('test_div1'))
suite.addTest(MyclassTest('test_div2'))
suite.addTest(MyclassTest('test_div3'))
suite.addTest(MyclassTest('test_div4'))
suite.addTest(MyclassTest('test_div5'))
suite.addTest(MyclassTest('test_pow'))
suite.addTest(MyclassTest('test_pow2'))
suite.addTest(MyclassTest('test_calculator1'))
suite.addTest(MyclassTest('test_calculator2'))
suite.addTest(MyclassTest('test_calculator3'))
suite.addTest(MyclassTest('test_108_109'))
runner = unittest.TextTestRunner()
runner.run(suite)
第一个用例选择没有负数,没有乘方,没有分数表示
第二个用例加了负数
第三个用例加了乘方
第四个用例是分数表示
第五个用例改了表达式个数,运算符个数,最大的数
第六个用例生成了1000道题
test_changPowOp测changePowOp,输入以^为乘方符的表达式,看输出是不是以**为乘方符的表达式
test_roundup1和test_roundup2测round_uptest_roundup1输入了一个最后一个小数位小于5的数,看输出是否把最后一位变成0test_roundup2输入的数最后一位小数位是5,看输出是否五入了
test_operadd,test_opersub,test_opermul,test_operdiv,test_operpow测试getOperOrder返回的运算符优先级,分别输入+,-,*,/,^,查看优先级。
test_add,test_sub,test_mul,test_div,test_pow的几个测试用例分别测solve的加法,减法,乘法,除法,乘方运算
test_add1输入两个数,查看做加法后的结果
test_add2输入两个分数
test_add3输入两个小数
test_sub1输入两个小数测试减法
test_sub2输入两个分数
test_mul1输入两个整数测试乘法
test_mul2输入一个正数一个负数
test_mul3是两个分数测试乘法
test_mul4是两个小数
test_div1是一个大一点的数除以小一点的数
test_div2是小一点的数除以大一点的数
test_div3是两个小数
test_div4是两个分数
test_div5是一个正数一个负数
test_pow是乘方运算
test_calculator是calculator函数的几种输入,看是否计算出预期的结果
单元测试共有36个测试用例,实现了所有路径的覆盖
Django部分的测试
Django中对login、register和题目生成进行了测试
login部分的测试
测试的用例如下
from django.test import TestCase
class LoginViewTests(TestCase):
def test_register(self):
"""先看看能不能打开注册界面"""
response = self.client.get("/register/")
self.assertEqual(response.status_code, 200)
def test_login(self):
"""先看看能不能打开登录界面"""
response = self.client.get("/login/")
self.assertEqual(response.status_code, 200)
首先对两个网页进行了测试,因为login中需要对数据库的数据进行验证,数据库中部分字段加密,register有随机生成的验证码,而无法得知随机生成的验证码信息,所以无法进行下一步测试,只能进行集成测试,暂时没有存在问题
题目生成部分的测试
reset_password部分的测试
重置密码有一个随机生成的字符串序列,这个序列是保存在数据库中的