python测试练习题(上提高你的Python能力)

对于程序开发新手来说,一个最常见的困惑是测试的主题。他们隐约觉得“单元测试”是很好的,而且他们也应该做单元测试。但他们却不懂这个词的真正含义。如果这听起来像是在说你,不要怕!在这篇文章中,我将介绍什么是单元测试,为什么它有用,以及如何对Python的代码进行单元测试。

什么是测试?

在讨论为什么测试很有用、怎样进行测试之前,让我们先花几分钟来定义一下“单元测试”究竟是什么。在一般的编程术语中,“测试”指的是通过编写可以调用的代码(独立于你实际应用程序的代码)来帮助你确定程序中是否有错误。这并不能证明你的代码是正确的(在非常有限的情况下这是唯一的可能)。它只是报告了测试者认为的那种情况是否被正确处理了。

注:当我使用“测试”一次时,我指的是“自动化测试”,即这些测试是在机器上运行的。“手动测试”则是一个人运行程序,并与它进行交互,从而发现漏洞,这是个独立的概念。

测试可以检查出什么样的情况呢?语法错误是语言的意外误用,如

你有两个函数,is_prime和print_next_prime。如果你想测试print_next_prime,我们就需要确定is_prime是正确的,因为print_next_prime中调用了这个函数。在这种情况下,print_next_prime函数是一个单元,is_prime函数是另一个单元。由于单元测试每次只测试一个单元,因此我们需要仔细考虑怎样才能准确的测试print_next_prime?(更多的是关于之后怎样实现这些测试)。

因此,测试代码应该长什么样呢?如果上一个例子存在一个叫primes.py的文件中,我们可以把测试代码写在一个叫test_primes.py的文件中。下面是test_primes.py 中的最基本内容,比如下面这个测试样例:

python测试练习题(上提高你的Python能力)(1)

这个文件通过一个test case:?test_is_five_prime. 创建了一个单元测试。通过Python内嵌的一个测试框架unittest。当unittest.main()被调用时,任何一个以test开头命名的成员函数将被运行,他们是unittest.TestCase的一个派生类,并且是断言检查的。如果我们通过输入python test_primes.py来运行测试,我们能够看到unittest框架在控制台上 的输出:

python测试练习题(上提高你的Python能力)(2)

单独的“E”表示的是我们单元测试的结果(如果它成功了,会打印出一个“.”)。我们可以看到我们的测试失败了,以及导致失败的那行代码,还有任何引发的异常信息。

为什么要测试?

在我们继续那个例子之前,要问个很重要的问题:“为什么测试对我来说有价值”?这是个公平的问题,也是那些对于代码测试不熟悉的人常问的问题。毕竟,测试需要一定的时间,而我们完全可以用这些时间去编代码,为什么要测试而不是去做那些最有生产效率的事?

有很多答案可以有效的回答这个问题,我列出了以下几点:

●测试可以保证你的代码在一系列给定条件下正常工作

测试确保了一系列条件下的正确性。语法错误基本上一定通过测试被查出来,一个代码单元的基本的逻辑也可以通过测试被检测出来,以确保一定条件下的正确性。再次,它不是要证明代码是在任何条件下都正确的。我们只是简单的瞄准了一套比较完整的可能的条件(例如,你可以写一个测试来监测当你调用my_addition_function(3, 'refrigerator), 的时候,但你不必为每个参数检测所有可能的字符串)

●测试允许人们确保对代码的改动不会破坏现有的功能

重构代码时,这一点特别有用。如果没有测试到位,你就没法保证你的代码的改变没有破坏之前工作正常的东西。如果你希望更改或重写你的代码,并希望不会破坏任何东西,适当的单元测试是很必要的。

●测试迫使人们在不寻常条件的情况下思考代码,这可能会揭示出逻辑错误

编写测试强迫你去思考在非正常条件下你的代码可能遇到的问题。在上面的例子中,my_addition_function函数可以将两个数字相加。测试基本正确性的简单测试将调用my_addition_function(2,2),并断言说结果是4。然而,进一步的测试可能会通过调用my_addition_function(2.0,2.0)来测试该功能是否能正确进行浮点数的运算。防御性的编码原则表明你的代码应该能够在非法输入的情况下正常失效,因此测试时,当字符串类型被作为参数传递到函数中时应当抛出一个异常。

●良好的测试要求模块化,解耦代码,这是一个良好的系统设计的标志

单元测试的整体做法是通过代码的松散耦合使其变得更容易。如果你的应用程序代码直接调用数据库,例如,测试你应用程序的逻辑依赖于一个有效的数据库连接,并且测试数据要存在于数据库中。另一方面,隔离了外部资源的代码在测试过程中更容易被模拟对象所替代。出于必要,(人们)设计的有测试能力的应用程序最终采用了模块化和松散耦合。

单元测试的剖析

通过继续之前的例子,我们将看到如何编写并组织单元测试。回想一下,primes.py包含以下代码:

python测试练习题(上提高你的Python能力)(3)

同时,文件test_primes.py包含如下代码:

python测试练习题(上提高你的Python能力)(4)

做出断言

unittest是Python标准库中的一部分,并且也是我们开始“单元测试之旅”的一个好的起点。一个单元测试中包括一个或多个断言(一些声明被测试代码的一些属性为真的语句)。会想你上学的时候“断言”这个词的字面意思就是“陈述事实”。在单元测试中,断言也是同样的作用。

self.assertTrue 更像是自我解释。它能声明传递过去的参数的计算结果为真。unittest.TestCase类包含了许多断言方法,所以一定要检查列表并选择合适的方法进行测试。如果在每个测试中都用到assertTrue的话,则应该考虑一个反模式,因为它增加了测试中读者的认知负担。正确使用断言的方法应当是使测试能够明确说明究竟是什么在被断言(例如,很明显?,只需扫一眼assertIsInstance 的方法名,就知道它要说明的是其参数)。

每个测试应该测试一个单独、有具体特性的代码,并且应该被赋予相关的命名。就单元测试发现机制的研究表明(主要在Python2.7 和3.2 版本中),测试方法应该以test_为前缀命名。(这是可配置的,但是其目的是鉴别测试方法和非测试的实用方法)。如果我们把test_is_five_prime 的命名改为is_five_prime的话,运行python中的test_primes.py时会输出如下信息:

python测试练习题(上提高你的Python能力)(5)

不要被上面信息中的“OK”所糊弄了,只有当什么测试都没真正运行的时候才会显示出“OK”!我认为一个测试也没跑其实应该显示个报错的,但是个人感觉放在一边,这是一个你应该注意是行为,尤其是当通过程序运行来检查测试结果的时候(例如,一个持续的集成工具,像TracisCI)。

异常

让我们回到test_primes.py的实际内容中去,回忆一下运行python test_primes.py指令后的输出结果:

python测试练习题(上提高你的Python能力)(6)

这些输出告诉我们,我们一个测试的结果失败并不是因为一个断言失败了,而是因为出现了一个未捕获的异常。事实上,由于抛出了一个异常,unittest框架并没有能够运行我们的测试就返回了。

这里的问题很明确:我们使用的求模运算的计算范围中包括了0,因此执行了一个除以0的操作。为了解决这个问题,我们可以很简单的将起始值由0变为2,并指出对0求模是错误的,而对1求模则一直是真(并且一个素数只能被自身和1整除,因此我们无需检查1)。

解决问题

一次失败的测试使我们修改了代码。一旦我们改好了这个错误(将s_prime中的一行改为for element in range(2, number):),我们就得到了如下输出:

python测试练习题(上提高你的Python能力)(7)

现在错误已经改了,这是不是意味着我们应该删掉test_is_five_prime这个测试方法(因为很明显,它将不会一直能通过测试)?不应该删。由于通过测试是最终目标的话单元测试应该尽量少的被删除。我们已经测试过is_prime的语法是有效的,并且,至少在一种情况下,它返回正确的结果。我们的目标是要建立一套能全部通过的(单元测试的逻辑分组)测试,虽然有些一开始可能会失败。

python测试练习题(上提高你的Python能力)(8)

请注意,这时我们给assert调用添加了可选的msg参数。如果该测试失败了,我们的信息将被打印到控制台,并给运行测试的人提供额外的信息。

未完待续。。。

End.

更多热门文章尽在51testing软件测试网!

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页