1)调试
程序写完后不可避免的有bug,我们需要不断的调试bug以达到程序的完美,那么我们需要一整套的调试程序的手段来修复bug。
(1)print
我们可以直接打印出可能有错误的变量。示例如下:
def fun(s):
n=int(s)
print('n=',n)
return 10/n
if __name__ == '__main__':
fun('0')输出结果如下:

(2)assert
assert可以用来代替print。示例代码如下:
def fun(s):
n=int(s)
assert n!=0,'n is 0'
return 10/n
if __name__ == '__main__':
fun('0')这里assert的意思是n!=0应该为真,否则就出错。如果assert为假,那么就抛出错误。输出结果如下:

2)测试
(1)单元测试
什么是单元测试呢?比如说我们完成了一个函数abs()。我们编写如下测试程序段:
在输入1,2,3的时候,返回1,2,3。
输入-1,-2,-3的时候,返回1,2,3。
输入0的时候,返回0。
输入非数值类型,返回TypeError。
将以上的程序段放到一个模块里,利用该模块不断试验完善程序的过程叫做单元测试。
如果单元测试通过,说明我们测试的这个函数能够正常工作。如果不正确那么需要调试。那么单元测试有什么意义呢?我们在每次对原函数(abs)修改后,可以用单元测试来确保原函数的功能没有被破坏。
举个例子:我们来编写一个Dict类,这个类的行为和dict一致,但是可以通过属性来访问,如下效果:
class Dict(dict):
def __init__(self,**kw):
super().__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict'object has no attribute'%s'"%key)
def __setattr__(self, key, value):
self[key]=value
d=Dict(a=1,b=2)
print(d['a'])
print(d.a)输出结果如下:
接下来,我们编写单元测试模块:
这里我们需要引入python自带的unittest模块,编写test.py如下:
import unittest
from exercise import Dict
class TestDict(unittest.TestCase):
def test_init(self):
d=Dict(a=1,b='test')
self.assertEqual(d.a,1)
self.assertEqual(d.b,'test')
self.assertTrue(isinstance(d,dict))
def test_key(self):
d=Dict()
d['key']='value'
self.assertEqual(d.keys,'value')
def test_attr(self):
d=Dict()
d.key='value'
self.assertTrue('key'in d)
self.assertEqual(d['key'],'value')
def test_keyerror(self):
d=Dict()
with self.assertRaises(AttributeError):
value=d.empty
def test_atterror(self):
d=Dict()
with self.assertRaises(AttributeError):
value=d.empty
if __name__ == '__main__':
unittest.main()输出结果如下:
这里,我们编写了5个测试程序。其中通过了4个,有一个失败了。TestDict类中以test开头的就是测试程序,不是test开头的就不是测试方法,测试的时候不会被执行。对每一类测试都需要编写一个test_xxx()方法。由于unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的。最常用的断言就是assertEqual():
self.assertEqual(abs(-1), 1) # 断言函数返回的结果与1相等
另一种重要的断言就是期待抛出指定类型的Error,比如通过d['empty']访问不存在的Key时,断言会抛出KeyError:
with self.assertRaises(KeyError):
value = d['empty']而通过d['empty']访问不存在的Key时,我们期待抛出AttributeError:
with self.assertRaises(AttributeError):
value = d.empty(2)文件测试
在Python的官方文档中,很多文档都有示例代码。如re模块就带了很多示例代码:
>>> import re
>>> m=re.search('(?<=abc)def','abcdef')
>>> m.group(0)
'def'可以把这些示例代码在python的交互式环境下输入并执行,结果与文档中的示例代码显示的一致。这些代码与其他说明可以写在注释中,然后由一些工具来自动生成文档。既然这些代码本身可以粘贴出来直接运行,那么我们不如直接直接运行注释中的代码,何必再粘贴呢?问题是这样可以吗?可以的话,又得怎么做呢?方法如下:
我们在编写注释的时候,写上这样的注释:
def abs(n):
'''
Function to get absolute value of number.
Example:
>>> abs(1)
1
>>> abs(-1)
1
>>> abs(0)
0
'''
return n if n >= 0 else (-n)那么程序员将更加理解这个函数的用法。并且,python内置的"文档测试"(doctest)模块可以直接提取注释中的代码并执行测试。
doctest严格按照python交互式命令行输入和输出来判断测试结果是否正确。只有测试异常的时候,可以用...来表示中间的错误提示输出。
PS:
这里,我们最后用了if __name__='__main__'结构。我们这里补充介绍一下:
一个python.py文件可以有两种使用方法,一种是作为脚本直接执行,另一种就是Import到其他的python脚本中被调用(模块重用)执行。而这段代码的作用就是来区分这两种使用方法:只有在第一种情况下,其冒号后面的代码才会执行。在第二种情况下,当该模块被import到其他脚本下,其冒号后面的代码是不会执行。这里举个例子:
我们在exercise.py中输入以下代码:
print("first")
if __name__=="__main__":
print("second")运行该exercise.py,结果如下:

在test.py中输入以下代码:
import exercise
结果如下:

这样结果就很明显了,第一个程序if __name__='__main__'前后面的代码都执行了,第二个程序只执行了前面的代码。
希望有志同道合的小伙伴关注我的公众平台,欢迎您的批评指正,共同交流进步。


本文介绍程序调试的方法,包括使用print和assert语句定位错误。同时深入探讨单元测试的重要性及实现方式,通过Python的unittest模块进行示例演示,并讨论文档测试(doctest)的应用。
441

被折叠的 条评论
为什么被折叠?



