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__'前后面的代码都执行了,第二个程序只执行了前面的代码。
希望有志同道合的小伙伴关注我的公众平台,欢迎您的批评指正,共同交流进步。