该用户从未签到
超凡大师
- 精华
- 0
- 威望
- 0
- 听众
- 0
- 萝卜
- 1402
- 注册时间
- 2020-2-9
- 在线时间
- 0 小时
|
本章我们先介绍软件测试的基本概念。为什么需要测试软件?一个测试软件如何运转的?如何判断测试是否成功?如何判断是否测试足够?在本章中,我们将回顾这些重要的概念,并同时熟悉Python的基本用法。
简单的测试让我们从一个简单的例子开始,您希望实现平方根函数 。(让我们暂时假设环境没用这一个小功能)在研究了Newton-Raphson方法之后,提出了以下Python代码,通过my_sqrt函数计算平方根。"""Computes the square root of x, using the Newton-Raphson method"""whileapprox != guess:approx = guessguess = (approx + x / approx) /2returnapprox现在,让我们看看此功能函数是否真正按照规范执行。
了解python如果您不熟悉Python,则可能首先必须了解以上代码的功能。我们非常推荐Python教程,以了解Python的工作原理。对于您来说,理解以上代码最重要的是以下三个:
whilePython是动态类型的,这意味着变量的类型像xguess在运行时才被确定。大多数的Python的句法特征是由其他语言启发的,如控制结构(,if),等号(=),或比较(==,就这样,你已经可以理解上面的代码做什么:用开始guess的x / 2,它计算好近似approx直到值approx不再改变。这是最终返回的值。运行功能函数为了确定是否my_sqrt正常工作,我们可以使用一些值对其进行测试。例如x = 4,对于,它将产生正确的值:my_sqrt(4 )2.0上面的上部my_sqrt(4)是Python解释器的输入,默认情况下对它进行运行。下部(2.0)是其输出。我们可以看到my_sqrt(4)这同样适用于:my_sqrt(2 )1.414213562373095调试功能函数为了解操作方式,一种简单的策略是print在关键位置插入语句。例如,您可以记录的值defmy_sqrt_with_log(x):"""Computes the square root of x, using the Newton–Raphson method"""print("approx =", approx)# <-- Newapprox = guessreturnapproxmy_sqrt_with_log(9)approx= Noneapprox =4.5approx =3.25approx =3.0096153846153846approx =3.000015360039322approx =3.00000000003932143.0这样我们就可以观察到每次运行时的内部细节,以排查问题。
检查功能函数让我们回到测试。我们可以阅读并运行代码,但是上面的值my_sqrt(2)实际上正确吗?我们可以通过利用 。让我们来看看:my_sqrt(2) * my_sqrt (2)1.9999999999999996看起来确实有一些四舍五入上的错误出现了
现在,我们已经测试了上面的程序:我们已经在给定的输入上执行了该程序,并检查了其结果是否正确。在程序投入生产之前,这种测试是质量保证的最低限度。
自动化测试执行到目前为止,我们已经手动测试了上述程序,即,手动运行它并手动检查其结果。这是一种非常灵活的测试方法,但是从长远来看,它效率很低:
手动测试,您只能检查非常有限的执行及其结果
对程序进行任何更改后,您必须重复测试过程
这就是为什么自动化测试非常有用的原因。一种简单的方法是让计算机首先进行计算,然后让计算机检查结果。
例如,这段代码自动测试:
result = my_sqrt(4)expected_result = 2.0if result == expected_result:else:Testpassed这个测试的好处是我们可以一次又一次地运行它,从而确保至少正确计算了4的平方根。但是,仍然存在许多问题:
单个测试需要五行代码
我们不在乎舍入错误
我们仅检查单个输入(和单个结果)
让我们一一解决这些问题。首先,让我们使测试更加紧凑。几乎所有的编程语言都可以自动检查条件是否成立,如果条件不成立则停止执行。这称为断言,对于测试非常有用。
在Python中,我们使用assert语句,如果条件为true,则什么也不会发生。(如果一切正常,则不应该出问题。)但是,如果条件评估为false,assert则会引发异常,表明测试刚刚失败。
在我们的示例中,我们可以assert轻松地检查是否my_sqrt产生了上述预期结果:
assertmy_sqrt(4)==2当您执行此行代码时,什么都不会发生:我们证明(或断言)我们确认了
但是请记住,浮点计算可能会导致舍入误差。因此,我们不能简单地比较两个浮点数是否相等。因此,我们将确保它们之间的绝对差保持在某个阈值以下,通常表示为或epsilon。这是我们可以做到的:EPSILON=1e-8assertabs(my_sqrt(4) -2) <EPSILONP.S. abs求绝对值
我们还可以为此目的引入一个特殊功能,现在对具体值进行更多测试:
defassertEquals(x, y, epsilon=1e-8):assertabs(x - y) < epsilonassertEquals(my_sqrt(4), 2)assertEquals(my_sqrt(9), 3)assertEquals(my_sqrt(100), 10)似乎可以工作,对吧?如果我们知道计算的预期结果,则可以一次又一次地使用此类断言,以确保我们的程序正确运行。
进一步生成测试还记得 普遍恒成立吗?我们还可以使用一些值来明确地测试它:
assertEquals(my_sqrt(2) *my_sqrt(2), 2)assertEquals(my_sqrt(3) *my_sqrt(3), 3)assertEquals(my_sqrt(42.11) *my_sqrt(42.11), 42.11)似乎仍然有效,对不对?最重要的是通过 我们可以测试成千上万的值:
forninrange(1,1000):assertEquals(my_sqrt(n) * my_sqrt(n), n)my_sqrt用100个值测试需要多少时间?让我们来看看。我们使用自己的Timer模块来测量经过时间。为了能够使用Timer,我们首先导入我们自己的实用程序模块。importbookutilsfromTimerimportTimer0.02291180900010658710,000个值大约需要百分之一秒,因此单次执行my_sqrt需要1/1000000秒或大约1微秒。
让我们重复随机选择10,000个值。Pythonrandom.random函数返回0.0到1.0之间的随机值:
importrandomx =1+ random.random *1000000assertEquals(my_sqrt(x) * my_sqrt(x), x)print(t.elapsed_time)0.02770466199990551一秒钟之内,我们现在测试了10,000个随机值,并且每次实际上都正确计算了平方根。只要对进行任何更改my_sqrt,我们就可以重复进行此测试,每次都可以增强我们my_sqrt没有隐患的信心。但是请注意,尽管随机函数在产生随机值方面没有偏见,但不太可能生成会极大改变程序行为的特殊值。我们将在下面稍后讨论。在运行时验证除了为编写和运行测试外my_sqrt,我们还可以将检查权限集成到实现中。这样,将自动检查每次调用现在,无论何时我们用…my_sqrt_checked(2.0)1.414213562373095我们已经知道结果是正确的,并且对于每次新的成功计算都是如此。
如上所述,自动运行时检查假设有两件事:
必须能够制定这种运行时检查。始终有可能要检查具体的值,但是以抽象的方式制定所需的属性可能非常复杂。在实践中,您需要确定哪些属性最关键,并为它们设计适当的检查。另外,运行时检查可能不仅取决于本地属性,还取决于程序状态的多个属性,所有这些属性都必须确定。
必须能够负担得起此类运行时检查。在的情况下my_sqrt,运行花费不是很大;但是,即使经过简单的操作,如果我们不得不检查大型数据结构,那么检查的费用很快就会变得昂贵。在实践中,通常会在生产过程中禁用运行时检查,以确保可靠性为代价。另一方面,一套全面的运行时检查是发现错误并快速调试它们的好方法。您需要确定在生产期间仍需要多少种这样的功能。系统输入与函数输入my_sqrt提供给其他程序员,然后他们可以将其嵌入他们的代码中。在某些时候,它必须处理来自第三方的输入,即不受程序员的控制。print('The root of', x,'is', my_sqrt(x))我们假设这是一个接受命令行输入的程序,如下所示:$sqrt_program 42我们可以对通过一些系统输入进行调用:sqrt_program("4")The rootof4is2.0这有什么问题?好吧,问题在于我们不检查外部输入的有效性。例如sqrt_program(-1)尝试调用。怎么办?实际上,如果您my_sqrt使用负数调用,它将进入无限循环。由于技术原因,本章不能有无限循环(除非我们希望代码永远运行)。因此,我们使用一种特殊的with ExpectTimeOut(1)构造在一秒钟后中断执行。fromExpectErrorimportExpectTimeoutwithExpectTimeout(1):sqrt_program("-1")Traceback (most recentcalllast):File"<ipython-input-25-add01711282b>", line2,in<module>sqrt_program("-1")File"<ipython-input-22-53e8ec8bb3ca>", line3,insqrt_programprint('The root of', x,'is', my_sqrt(x))File"<ipython-input-1-47185ad159a1>", line7,inmy_sqrtguess = (approx + x / approx) /2File, line7,in2File"ExpectError.ipynb", line59,incheck_timeTimeoutError (expected)上面的消息是错误消息,表明出了点问题。它列出了错误发生时处于活动状态的函数和行的调用堆栈。最底部的行是最后执行的行;上面的几行代表函数调用–在我们的例子中,最大为我们不希望我们的代码以异常终止。因此,在接受外部输入时,我们必须确保已对其进行正确验证。我们可以写例如:
defsqrt_program(arg):ifx <0:然后我们可以确保my_sqrt仅根据其规范进行调用。sqrt_program(“ -1” )IllegalInput可是等等!如果sqrt_program不使用数字调用怎么办?然后,我们将尝试转换非数字字符串,这也会导致运行时错误:fromExpectErrorimportExpectErrorwithExpectError:sqrt_program("xyzzy")Traceback (most recentcalllast):File"<ipython-input-29-8c5aae65a938>", line2,in<module>sqrt_program("xyzzy")File"<ipython-input-26-ea86281b33cf>", line2,insqrt_programx =int(arg)ValueError: invalid literalforintwithbase10:'xyzzy'(expected)else:sqrt_program("4")The rootof4.0is2.0sqrt_program("-1")Illegal Numbersqrt_program("xyzzy")Illegal Input现在我们已经看到,在系统级别,程序必须能够优雅地处理任何类型的输入,而永远不会进入不受控制的状态。当然,这对于程序员来说是负担,他们必须努力使自己的程序在所有情况下都健壮起来。但是,这种负担在生成软件测试时会成为一个好处:如果程序可以处理任何类型的输入(可能带有定义良好的错误消息),我们也可以将其发送给任何类型的输入。但是,当使用生成的值调用函数时,我们必须知道其精确的先决条件。
测试的极限尽管在测试方面付出了最大的努力,但请记住,您始终在检查功能是否有一组有限的输入。因此,可能总是存在未经测试的输入,其功能可能仍会失败。
在的情况下Traceback (most recentcalllast):File"<ipython-input-34-24ede1f53910>", line2,in<module>root = my_sqrt(0)File"<ipython-input-1-47185ad159a1>", line7,inmy_sqrtguess = (approx + x / approx) /2ZeroDivisionError:floatdivisionbyzero (expected)到目前为止,在我们的测试中,我们还没有检查这种情况,这意味着要建立在 会出人意料地失败。但是,即使我们将随机生成器设置为产生0–1000000而不是1–1000000的输入,它偶然产生零值的机会仍然是百万分之一。如果对于几个单独的值,函数的行为完全不同,则纯随机测试几乎没有机会产生这些值。
当然,我们可以相应地修复功能,记录可接受的值x并处理特殊情况ifx ==0:return0returnmy_sqrt(x)这样,我们现在可以正确地计算
assertmy_sqrt_fixed(0)==0withExpectError:root = my_sqrt_fixed(-1)仍然,我们必须记住,尽管广泛的测试可以使我们对程序的正确性有很高的信心,但它不能保证所有将来的执行都是正确的。甚至检查每个结果的运行时验证也只能保证,如果产生一个结果,那么结果将是正确的。但不能保证将来的执行不会导致检查失败。在撰写本文时,我相信这my_sqrt_fixed(x)是,但我不能100%确定。经验教训测试的目的是执行一个程序,以便我们发现错误。
测试执行,测试生成和检查测试结果可以自动化。
测试不完整; 它不提供100%保证代码没有错误的保证。
下一步构建你自己的模糊测试——用随机输入测试程序。
|
|