提高你的Python本领:领略单位测试
对付措施开拓新手来说,一个最常见的狐疑是测试的主题。他们隐约以为“单位测试”是很好的,并且他们也应该做单位测试。但他们却不懂这个词的真正寄义。假如这听起来像是在说你,不要怕!在这篇文章中,我将先容什么是单位测试,为什么它有用,以及如何对Python的代码举办单位测试。
什么是测试?
在接头为什么测试很有用、奈何举办测试之前,让我们先花几分钟来界说一下“单位测试”毕竟是什么。在一般的编程术语中,“测试”指的是通过编写可以挪用的代码(独立于你实际应用措施的代码)来辅佐你确定措施中是否有错误。这并不能证明你的代码是正确的(在很是有限的环境下这是独一的大概)。它只是陈诉了测试者认为的那种环境是否被正确处理惩罚了。
注:当我利用“测试”一次时,我指的是“自动化测试”,即这些测试是在呆板上运行的。“手动测试”则是一小我私家运行措施,并与它举办交互,从而发明裂痕,这是个独立的观念。
测试可以查抄出什么样的环境呢?语法错误是语言的意外误用,如
my_list..append(foo)
后头多余的一个 “.“。逻辑错误是当算法(可以当作是“办理问题的方法”)不正确时激发的。大概措施员健忘Python是“零索引“的而且试图通过写
print(my_string[len(my_string)])
(这样会引起IndexError)来打印出一个字符串中的最后一个字符。更大、更系统的错误也可以被查抄出来。好比当用户输入一个大于100的数字、可能在网站检索不行用的时候挂起此网站的话,措施会一直瓦解。
这些所有的错误都可以通过对代码的仔细测试查抄出来。Unit testing,特指在一个脱离的代码单位中的测试。一个单位可以是整个模块,一个单独的类可能函数,可能这两者间的任何代码。然而,重要的是,测试代码要与我们没有测试到的其他代码彼此断绝(因为其它代码自己有错误的话会因此夹杂测试功效)。思量如下例子:
def is_prime(number): """Return True if *number* is prime.""" for element in range(number): if number % element == 0: return False return True def print_next_prime(number): """Print the closest prime number larger than *number*.""" index = number while True: index += 1 if is_prime(index): print(index)
你有两个函数,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 中的最根基内容,好比下面这个测试样例:
import unittest from primes import is_prime class PrimesTestCase(unittest.TestCase): """Tests for `primes.py`.""" def test_is_five_prime(self): """Is five successfully determined to be prime?""" self.assertTrue(is_prime(5)) if __name__ == '__main__': unittest.main()
这个文件通过一个test case:?test_is_five_prime. 建设了一个单位测试。通过Python内嵌的一个测试框架 unittest。当unittest.main()被挪用时,任何一个以test开头定名的成员函数将被运行,他们是unittest.TestCase的一个派生类,而且是断言查抄的。假如我们通过输入python test_primes.py来运行测试,我们可以或许看到unittest框架在节制台上 的输出:
$ python test_primes.py E ====================================================================== ERROR: test_is_five_prime (__main__.PrimesTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "test_primes.py", line 8, in test_is_five_prime self.assertTrue(is_prime(5)) File "/home/jknupp/code/github_code/blug_private/primes.py", line 4, in is_prime if number % element == 0: ZeroDivisionError: integer division or modulo by zero ---------------------------------------------------------------------- Ran 1 test in 0.000s
#p#分页标题#e#
单独的“E”暗示的是我们单位测试的功效(假如它乐成了,会打印出一个“.”)。我们可以看到我们的测试失败了,以及导致失败的那行代码,尚有任何激发的异常信息。
为什么要测试?
在我们继承谁人例子之前,要问个很重要的问题:“为什么测试对我来说有代价”?这是个公正的问题,也是那些对付代码测试不熟悉的人常问的问题。究竟,测试需要必然的时间,而我们完全可以用这些时间去编代码,为什么要测试而不是去做那些最有出产效率的事?
有许多谜底可以有效的答复这个问题,我列出了以下几点:
测试可以担保你的代码在一系列给定条件下正常事情
测试确保了一系列条件下的正确性。语法错误根基上必然通过测试被查出来,一个代码单位的根基的逻辑也可以通过测试被检测出来,以确保必然条件下的正确性。再次,它不是要证明代码是在任何条件下都正确的。我们只是简朴的对准了一套较量完整的大概的条件(譬喻,你可以写一个测试来监测当你挪用my_addition_function(3, 'refrigerator), 的时候,但你不必为每个参数检测所有大概的字符串)
测试答允人们确保对代码的窜改不会粉碎现有的成果
重构代码时,这一点出格有用。假如没有测试到位,你就没法担保你的代码的改变没有粉碎之前事情正常的对象。假如你但愿变动或重写你的代码,并但愿不会粉碎任何对象,适当的单位测试是很须要的。
测试迫使人们在不寻常条件的环境下思考代码,这大概会展现出逻辑错误
编写测试强迫你去思考在非正常条件下你的代码大概碰着的问题。在上面的例子中,my_addition_function函数可以将两个数字相加。测试根基正确性的简朴测试将挪用my_addition_function(2,2),并断言说功效是4。然而,进一步的测试大概会通过挪用my_addition_function(2.0,2.0)来测试该成果是否能正确举办浮点数的运算。防止性的编码原则表白你的代码应该可以或许在犯科输入的环境下正常失效,因此测试时,当字符串范例被作为参数通报到函数中时该当抛出一个异常。
精采的测试要求模块化,解耦代码,这是一个精采的系统设计的符号
单位测试的整体做法是通过代码的松散耦合使其变得更容易。假如你的应用措施代码直接挪用数据库,譬喻,测试你应用措施的逻辑依赖于一个有效的数据库毗连,而且测试数据要存在于数据库中。另一方面,断绝了外部资源的代码在测试进程中更容易被模仿工具所替代。出于须要,(人们)设计的有测试本领的应用措施最终回收了模块化和松散耦合。
单位测试的分解
通过继承之前的例子,我们将看到如何编写并组织单位测试。追念一下,primes.py包括以下代码:
def is_prime(number): """Return True if *number* is prime.""" for element in range(number): if number % element == 0: return False return True def print_next_prime(number): """Print the closest prime number larger than *number*.""" index = number while True: index += 1 if is_prime(index): print(index)
同时,文件test_primes.py包括如下代码:
import unittest from primes import is_prime class PrimesTestCase(unittest.TestCase): """Tests for `primes.py`.""" def test_is_five_prime(self): """Is five successfully determined to be prime?""" self.assertTrue(is_prime(5)) if __name__ == '__main__': unittest.main()
做出断言
#p#分页标题#e#
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 test_primes.py ---------------------------------------------------------------------- Ran 0 tests in 0.000s OK
不要被上面信息中的“OK”所瞎搅了,只有当什么测试都没真正运行的时候才会显示出“OK”!我认为一个测试也没跑其实应该显示个报错的,可是小我私家感受放在一边,这是一个你应该留意是行为,尤其是当通过措施运行来查抄测试功效的时候(譬喻,一个一连的集成东西,像TracisCI)。
异常
让我们回到test_primes.py的实际内容中去,回想一下运行python test_primes.py指令后的输出功效:
$ python test_primes.py E ====================================================================== ERROR: test_is_five_prime (__main__.PrimesTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "test_primes.py", line 8, in test_is_five_prime self.assertTrue(is_prime(5)) File "/home/jknupp/code/github_code/blug_private/primes.py", line 4, in is_prime if number % element == 0: ZeroDivisionError: integer division or modulo by zero ---------------------------------------------------------------------- Ran 1 test in 0.000s
这些输出汇报我们,我们一个测试的功效失败并不是因为一个断言失败了,而是因为呈现了一个未捕捉的异常。事实上,由于抛出了一个异常,unittest框架并没有可以或许运行我们的测试就返回了。
这里的问题很明晰:我们利用的求模运算的计较范畴中包罗了0,因此执行了一个除以0的操纵。为了办理这个问题,我们可以很简朴的将起始值由0变为2,并指出对0求模是错误的,而对1求模则一直是真(而且一个素数只能被自身和1整除,因此我们无需查抄1)。
办理问题
一次失败的测试使我们修改了代码。一旦我们改好了这个错误(将s_prime中的一行改为for element in range(2, number):),我们就获得了如下输出:
$ python test_primes.py . ---------------------------------------------------------------------- Ran 1 test in 0.000s
此刻错误已经改了,这是不是意味着我们应该删掉test_is_five_prime这个测试要领(因为很明明,它将不会一直能通过测试)?不该该删。由于通过测试是最终方针的话单位测试应该只管少的被删除。我们已经测试过is_prime的语法是有效的,而且,至少在一种环境下,它返回正确的功效。我们的方针是要成立一套能全部通过的(单位测试的逻辑分组)测试,固然有些一开始大概会失败。
test_is_five_prime用于处理惩罚一个“非非凡”的素数。让我们确保它也能正确处理惩罚非素数。将以下要领添加到PrimesTestCase类:
def test_is_four_non_prime(self): """Is four correctly determined not to be prime?""" self.assertFalse(is_prime(4), msg='Four is not prime!')
请留意,这时我们给assert挪用添加了可选的msg参数。假如该测试失败了,我们的信息将被打印到节制台,并给运行测试的人提供特另外信息。
界线环境
#p#分页标题#e#
我们已经乐成的测试了两种普通环境。此刻让我们思量界线环境下、可能那些不寻常或意外的输入的用例。当测试一个其范畴是正整数的函数时,界线环境下的实例包罗0、1、负数和一个很大的数字。此刻让我们来测试个中的一些。
添加一个对0的测试很简朴。我们估量?is_prime(0)返回的是false,因为,按照界说,素数必需大于1。
def test_is_zero_not_prime(self): """Is zero correctly determined not to be prime?""" self.assertFalse(is_prime(0))
惋惜呀,输出是:
python test_primes.py ..F ====================================================================== FAIL: test_is_zero_not_prime (__main__.PrimesTestCase) Is zero correctly determined not to be prime? ---------------------------------------------------------------------- Traceback (most recent call last): File "test_primes.py", line 17, in test_is_zero_not_prime self.assertFalse(is_prime(0)) AssertionError: True is not false ---------------------------------------------------------------------- Ran 3 tests in 0.000s FAILED (failures=1)
0被错误的鉴定为素数。我们健忘了,我们抉择在数字范畴中跳过0和1。让我们增加一个对他们的非凡查抄。
def is_prime(number): """Return True if *number* is prime.""" if number in (0, 1): return False for element in range(2, number): if number % element == 0: return False return True
此刻测试通过了。我们的函数应该奈何处理惩罚一个负数?在写这个测试用例之前就知道输出功效是很重要的。在这种环境下,任何负数都应该返回false。
def test_negative_number(self): """Is a negative number correctly determined not to be prime?""" for index in range(-1, -10, -1): self.assertFalse(is_prime(index))
这里我们以为查抄从-1到-9的所有数字。在一个轮回中挪用test要领长短常正当的,在一个测试中多次挪用断言要领也可以。我们可以在下面用(更具体)的方法改写代码。
def test_negative_number(self): """Is a negative number correctly determined not to be prime?""" self.assertFalse(is_prime(-1)) self.assertFalse(is_prime(-2)) self.assertFalse(is_prime(-3)) self.assertFalse(is_prime(-4)) self.assertFalse(is_prime(-5)) self.assertFalse(is_prime(-6)) self.assertFalse(is_prime(-7)) self.assertFalse(is_prime(-8)) self.assertFalse(is_prime(-9))
这两个是完全等价的。除了当我们运行轮回版本时,我们获得了一个我们不太想要的信息:
python test_primes.py ...F ====================================================================== FAIL: test_negative_number (__main__.PrimesTestCase) Is a negative number correctly determined not to be prime? ---------------------------------------------------------------------- Traceback (most recent call last): File "test_primes.py", line 22, in test_negative_number self.assertFalse(is_prime(index)) AssertionError: True is not false ---------------------------------------------------------------------- Ran 4 tests in 0.000s FAILED (failures=1)
#p#分页标题#e#
嗯···我们知道测试失败了,可是是在哪个负数上失败的?很是没用的是,Python的单位测试框架并没有打印出预期值和实际值。我们可以移步到两种方法上,并用个中之一来办理问题:通过msg参数,或通过利用一个第三方的单位测试框架。
利用msg参数来assertFalse仅仅可以或许使我们认识到我们可以用字符串的名目配置来办理问题。
def test_negative_number(self): """Is a negative number correctly determined not to be prime?""" for index in range(-1, -10, -1): self.assertFalse(is_prime(index), msg='{} should not be determined to be prime'.format(index))
从而给出了如下输出信息:
python test_primes ...F ====================================================================== FAIL: test_negative_number (test_primes.PrimesTestCase) Is a negative number correctly determined not to be prime? ---------------------------------------------------------------------- Traceback (most recent call last): File "./test_primes.py", line 22, in test_negative_number self.assertFalse(is_prime(index), msg='{} should not be determined to be prime'.format(index)) AssertionError: True is not false : -1 should not be determined to be prime ---------------------------------------------------------------------- Ran 4 tests in 0.000s FAILED (failures=1)
妥善地修复代码
我们看到,失败的负数是第一个数字:-1。为了办理这个问题,我们可觉得负数增再增加一个非凡查抄,可是编写单位测试的目标不是盲目标添加代码来检测界线环境。当一个测试失败时,我们应该退后一步而且确定办理问题的最佳方法。在这种环境下,我们就不应增加一个特另外if:
def is_prime(number): """Return True if *number* is prime.""" if number < 0: return False if number in (0, 1): return False for element in range(2, number): if number % element == 0: return False return True
该当首先利用如下代码:
def is_prime(number): """Return True if *number* is prime.""" if number <= 1: return False for element in range(2, number): if number % element == 0: return False return True
在后一个代码中,我们发明假如参数小于便是1时,两个if语句可以归并到一个返回值为false的语句中。这样做不只越发简捷,而且很好的贴合了素数的界说(一个比1大而且只能被1和它自己整除的数)。
第三方测试框架
我们原来也可以通过利用第三方测试框架办理这个由于信息太少导致测试失败的问题。最常用的两个是py.test和nose。通过运行语句py.test -l(-l为显示局部变量的值)可以获得如下功效。
#! bash py.test -l test_primes.py ============================= test session starts ============================== platform linux2 -- Python 2.7.6 -- pytest-2.4.2 collected 4 items test_primes.py ...F =================================== FAILURES =================================== _____________________ PrimesTestCase.test_negative_number ______________________ self = def test_negative_number(self): """Is a negative number correctly determined not to be prime?""" for index in range(-1, -10, -1): > self.assertFalse(is_prime(index)) E AssertionError: True is not false index = -1 self = test_primes.py:22: AssertionError
#p#分页标题#e#
正如你所看到的,一些更有用的信息。这些框架提供了比纯真的更具体的输出更多的成果,但问题是仅仅知道它们能存在和扩展内置unittest测试包的成果。
竣事语
在这篇文章中,你学到了什么是单位测试,为什么它们如此重要,尚有奈何编写测试。这就是说,要留意我们只是剖开了测试要领学中的表层,更多高级的话题,好比测试案例的组织、一连整合以及测试案例的打点等都是可供那些想要进一步进修Python中的测试的读者研究的很好的话题。
在不改变其成果的前提下重组/清理代码
编代码时不袒露其内部数据或函数而且不利用其他代码的内部数据或函数
文章转自:http://blog.jobbole.com/55180/ 作者:卷卷怪
英文出处:http://jeffknupp.com/blog/2013/12/09/improve-your-python-understanding-unit-testing/