python学习笔记(十三)调试,单元测试,文档测试(这个有问题 ,没调成功)

    技术2022-07-12  63

    调试

    程序一次写完不出错的概率很小,有的bug看错误信息就知道,有的变量值是错误的,因此 需要一整套调试程序的手段来修复bug。 第一种方法简单直接粗暴有效,就是print()把可能有问题的变量打印出来:

    def foo(s): n = int(s) print('>>>n = %d' % n) return 10 / n def main(): foo('0') main()

    用point的最大坏处是还得删掉他,如果程序里都是print(),运行结果也会有很多垃圾信息。

    断言

    判断一下,然后输出 句话

    def foo(s): n = int(s) assert n != 0, 'n is Zero!' return 10/n def main(): foo('0') main()

    结果:

    Traceback (most recent call last): File "G:/zrx/demoproject/demo1.py", line 734, in <module> main() File "G:/zrx/demoproject/demo1.py", line 732, in main foo('0') File "G:/zrx/demoproject/demo1.py", line 728, in foo assert n != 0, 'n is Zero!' AssertionError: n is Zero!

    虽然也充满 输出语句 不过启动python解释器时可以用参数-O来关闭assert $ python -O err.py

    Traceback (most recent call last): … ZeroDivisionError: division by zero

    logging

    把print()替换为logging是第三种方式,logging不会抛出错误,而且可以 输出到文件:

    import logging logging.basicConfig(level = logging.INFO) s = '0' n = int(s) logging.info('n = %d' % n) print( 10 / n )

    这就是logging的好处,它允许你指定记录信息的级别,有debug,info,warning,error等 几个级别,当我们指定level = INFO时,logging.debug就不起作用了。 同理,指定level = WARNING后,debug和info 就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。

    pdb

    第四种是启动Python的 调试器pdb,让程序以单步方式 运行,可以随时查看运行状态。我们先准备好程序:

    import pdb s = '0' n = int(s) #pdb.set_trace() 算是一个断点,执行到这里,但输出下一句代码,需要import pdb print(10 / n)

    在命令行 pdb命令简介:https://www.cnblogs.com/Zzbj/p/10592278.html

    IDE 如果要比较爽地设置断点、单步执行,就需要一个支持调试功能的IDE。目前比较好的Python IDE有: Visual Studio Code:https://code.visualstudio.com/,需要安装Python插件。 PyCharm:http://www.jetbrains.com/pycharm/

    单元测试

    单元测试就是用来对一个模块、一个函数或者一个类来进行 正确性检验工作。

    比如对函数abs(),我们可以编写出以下几个测试用例: 1.输入正数,比如1、1.2、0.99,期待返回值与输入相同; 2.输入负数,比如-1、-1.2、-0.99,期待返回值与输入相反; 3.输入0,期待返回0; 4.输入非数值类型,比如None、[]、{},期待抛出TypeError。

    把上面的测试放到一个测试模块里,就是一个完整的单元测试。 如果单元测试通过,说明我们的这个函数能正常工作。如果单元测试不通过,要么 函数有bug,要么测试条件不对。 单元测试的意义:如果我们对abs()函数代码做了修改,只要 再跑一遍单元测试,若通过,说明修改不会对abs()原有行为 造成影响。 以测试为驱动的开发模式,最大的好处就是确保一个程序模块的 行为符合我们 设计的测试用例。在将来修改的时候,可以极大程度地保证该模块行为仍然是正确的。 例:

    class Dict(dict): def __init__(self, **kw): #初始化内置函数 super().__init__(**kw) #就是dict的初始化 def __getattr__(self, key): #get方法,用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 import unittest #为了继承unittest.TestCase class TestDict(unittest.TestCase): def test_init(self): #初始化单元测试 d = Dict(a=1, b='test') self.assertEqual( d.a, 1) #看函数返回值与1是否相等 self.assertEqual( d.b, 'test') self.assertTrue(isinstance(d, dict)) #看d是不是dict类型 def test_key(self): #关键字单元测试 d = Dict() d['key'] = 'value' self.assertEqual(d.key, 'value') #有点乱 def test_attr(self): d = Dict() d.key = 'value' self.assertTrue('key' in d) #看key是不是在d里面 self.assertEqual(d['key'], 'value') #看value是否能正确存储,与key对应 def test_keyerror(self): d = Dict() with self.assertRaises(KeyError): #指定keyError,通过d['empty']访问不存在的key时,断言会抛出KeyError value = d['empty'] def test_attrerror(self): # d = Dict() with self.assertRaises(AttributeError): #通过d.empty访问不存在的key时,我们期待抛出AttributeError value = d.empty

    编写单元测试时,我们需要编写一个测试类,从unittest.TestCase继承。 默认以test开头的方法就是测试方法,不是test开头的 方法测试的时候不会被执行。 对于每一类测试都要编写 一个test_xxx()方法 。由于unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的。最常用的断言就是assertEqual():

    self.assertEqual(abs(-1),1) #断言函数返回的结果与1相等

    另一种重要的断言就是期待抛出指定类型的Error,比如通过d['empty']访问不存在的key时,断言会抛出KeyError:

    with self.assertRaises(AttributeError): value = d.empty

    运行单元测试 编写好 就可以运行单元测试。最简单的运行方式是在demo1.py的最后加上 两行代码

    if __name__ == '__main__': #虽然不知道为什么是这两句,但是好像前面看到过是用测试的 unittest.main()

    结果: setUp与tearDown

    在单元测试中写两个特殊的setUp()和tearDown()方法。这两个一个在测试方法前调用,一个在测试方法后调用。 假如测试需要启动一个数据库,就可以在setUp()方法中连接数据库,在tearDown()中关闭数据库,这样,不必在每个方法中重复相同的代码: (在刚刚那个测试类 里面 加这两个函数)

    def setUp(self) : print('setUp......') def tearDown(self) : print('tearDown....')

    会输出5遍,每个 test方法前后都会输出。 练习: 对Student类编写单元测试,结果发现测试不通过,请修改Student类,让测试通过:

    # -*- coding: utf-8 -*- import unittest class Student(object): def __init__(self, name, score): self.name = name self.score = score def get_grade(self): if self.score >= 60: return 'B' if self.score >= 80: return 'A' return 'C' class TestStudent(unittest.TestCase): def test_80_to_100(self): s1 = Student('Bart', 80) s2 = Student('Lisa', 100) self.assertEqual(s1.get_grade(), 'A') self.assertEqual(s2.get_grade(), 'A') def test_60_to_80(self): s1 = Student('Bart', 60) s2 = Student('Lisa', 79) self.assertEqual(s1.get_grade(), 'B') self.assertEqual(s2.get_grade(), 'B') def test_0_to_60(self): s1 = Student('Bart', 0) s2 = Student('Lisa', 59) self.assertEqual(s1.get_grade(), 'C') self.assertEqual(s2.get_grade(), 'C') def test_invalid(self): s1 = Student('Bart', -1) s2 = Student('Lisa', 101) with self.assertRaises(ValueError): s1.get_grade() with self.assertRaises(ValueError): s2.get_grade() if __name__ == '__main__': unittest.main()

    把get_grade()改了即可

    def get_grade(self): if self.score >= 60 and self.score < 80: return 'B' if self.score >= 80 and self.score <= 100: return 'A' if self.score >= 0 and self.score < 60: return 'C' if self.score < 0 or self.score > 100: raise ValueError('input error!')

    结果:

    文档测试

    如果你经常阅读Python文档,可以看到很多文档都有示例,比如re模块就带了 很多示例,不好意思 ,我没看过Python文档 。

    import re m = re.search('(?<=abc)def', 'abcdef') #没看懂这句话啥意思 print(m.group(0))

    这些代码与其它说明可以写在注释中,然后由一些工具自动生成文档。既然这些代码本身就可以粘贴出来直接运行,那么也可以自动执行写在注释中的这些代码。 def abs(n): ‘’’ Functio to absolute value of number. Example >>>abs(1) 1 >>>abs(-1) 1 ‘’’ return n if n >= 0 else (-n) 这样可以更明确地告诉函数调用者该函数的输入输出与功能。 python内置的doctest‘文档测试’模块可以直接提取注释中的代码并执行测试。 doctest严格按照Python交互式命令行输入和输出来判断测试结果是否正确。只有测试异常的时候,可以用。。。表示中间一大段烦人的输出。 例:用doctest测试刚刚的Dict类 什么输出也没有。这说明我们编写的doctest运行都是正确的。如果程序有问题,比如把__getattr__()方法注释掉,再运行就会报错。

    class Dict(dict): ''' Simple dict but also support access as x.y style. >>>d1 = Dict() >>>d1['x'] = 100 >>>d1.x 100 >>>d1.y = 200 >>>d1['y'] 200 >>>d2 = Dict(a=1, b=2, c='3') >>>d2.c '3' >>>d2['empty'] Traceback(most recent call last): ... KeyError:'empty' >>>d2.empty Traceback(most recent call last) ... AttributeError:'Dict' object has no attribute 'empty' ''' # def __init__(self, **kw): #初始化内置函数 # super().__init__(**kw) #就是dict的初始化 def __getattr__(self, key): #get方法,用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

    因为我把 __getattr__方法注释掉了,按理说会在命令行输出错误信息,但是出的错误我不会改,跟教程 不一样T T 结果:

    Traceback (most recent call last): File "G:/zrx/demoproject/demo1.py", line 908, in <module> doctest.testmod() File "G:\zrx\python\lib\doctest.py", line 1955, in testmod for test in finder.find(m, name, globs=globs, extraglobs=extraglobs): File "G:\zrx\python\lib\doctest.py", line 939, in find self._find(tests, obj, name, module, source_lines, globs, {}) File "G:\zrx\python\lib\doctest.py", line 1001, in _find self._find(tests, val, valname, module, source_lines, File "G:\zrx\python\lib\doctest.py", line 989, in _find test = self._get_test(obj, name, module, globs, source_lines) File "G:\zrx\python\lib\doctest.py", line 1073, in _get_test return self._parser.get_doctest(docstring, globs, name, File "G:\zrx\python\lib\doctest.py", line 675, in get_doctest return DocTest(self.get_examples(string, name), globs, File "G:\zrx\python\lib\doctest.py", line 689, in get_examples return [x for x in self.parse(string, name) File "G:\zrx\python\lib\doctest.py", line 651, in parse self._parse_example(m, name, lineno) File "G:\zrx\python\lib\doctest.py", line 709, in _parse_example self._check_prompt_blank(source_lines, indent, name, lineno) File "G:\zrx\python\lib\doctest.py", line 793, in _check_prompt_blank raise ValueError('line %r of the docstring for %s ' ValueError: line 3 of the docstring for __main__.Dict lacks blank after >>>: '>>>d1 = Dict()'

    Process finished with exit code 1 另外,当模块正常导入时,doctest不会被执行,只有命令行直接运行时,才执行doctest。所以,不必担心doceest会在非测试环境下执行。

    Processed: 0.017, SQL: 9