Python(10)错误、调试、测试(下)

简介: Python(10)错误、调试、测试(下)

三、调试


  • 在实际工作中,一次性能写完代码并且可以正常运行的概率很小,因为总会有各种各样的bug需要处理
  • 有的bug很简单,看看错误输出就可以解决,有的bug很复杂,需要知道出错时,哪些变量是正确的,哪些变量的值是错误的,因此,需要一整套调试程序的手段来修复bug
  • 下面来看几种调试的方法:


- print


  • 第一种方法简单粗暴,就是把print()把可能有问题的变量打印出来看看,例如:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def foo(s):
    num = int(s)
    print('>>>num = %s' % num)
    return print(10 / num)
def main():
    foo('0')
main()
#输出
>>>num = 0    #输出了num变量的值
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 11, in <module>
    main()
  File "f:\MY_python\Python\test2.py", line 9, in main
    foo('0')
  File "f:\MY_python\Python\test2.py", line 6, in foo
    return print(10 / num)
ZeroDivisionError: division by zero
- 但是使用'print()'最大的坏处就是在调试完代码之后,需要把'print()'语句注释或者删除掉,一想到程序里各种'print()',运行结果里面包含很多打印的无关信息,这个时候我们可以使用第二种方法

- 断言assert


  • 只要是使用了print()语句来辅助查看的地方,都可以使用断言(assert)来代替,例如:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def foo(s):
    num = int(s)
    assert num != 0,'num is 0 !!'
    return print(10 / num)
def main():
    foo('0')
main()
#输出
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 11, in <module>
    main()
  File "f:\MY_python\Python\test2.py", line 9, in main
    foo('0')
  File "f:\MY_python\Python\test2.py", line 5, in foo
    assert num != 0,'num is 0 !!'
AssertionError: num is 0 !!   #最后输出的是AssertionError错误类型
- 以assert num != 0,'num is 0 !!'为例,来看一下assert的使用方法: assert num != 0 ,当表达式num != 0 为'False'时,就会输出后面的语句'num is 0 !!',而assert本身输出的错误类型是AssertionError,

但是如果只是单纯的把print()换成了assert(),其实也好不到哪去,但是在启动Python解释器可以使用-O参数来关闭assert,例如:

- 加-O来执行py文件
PS F:\MY_python\Python> & C:/Users/12488/AppData/Local/Microsoft/WindowsApps/python3.10.exe -O f:/MY_python/Python/test2.py
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 11, in <module>
    main()
  File "f:\MY_python\Python\test2.py", line 9, in main
    foo('0')
  File "f:\MY_python\Python\test2.py", line 6, in foo
    return print(10 / num)
ZeroDivisionError: division by zero  #可以看到错误类型不再是AssertionError
  • 关闭assert后,可以把所有的assert语句当作pass看待


- logging


  • print()替换为logging是第三种方式,和assert相比,logging不会抛出错误,但是可以输出到文件,例如:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10/n)
#输出
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 8, in <module>
    print(10/n)
ZeroDivisionError: division by zero
- 使用'logging.info()'就可以输出一段文本,但是从上面的代码可以看出来,使用了'logging.info()'之后并没有输出相应的文本,这是因为logging有等级之分,这个时候需要再加一段代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10/n)
#输出
INFO:root:n = 0
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 9, in <module>
    print(10/n)
ZeroDivisionError: division by zero
- 成功输出了相应文本

使用logging时,允许指定记录信息的级别,总共有debug、info、warning、error、critical等五个级别,其中默认级别是warning,上面使用了INFO级别


日志等级说明:

'DEBUG':程序调试bug时使用
'INFO':程序正常运行时使用
'WARNING':程序未按预期运行时使用,但并不是错误,例如:用户登录时密码错误
'ERROR':程序出错误时使用,例如:IO操作失败
'CRITICAL':特别严重的问题,导致程序不能再继续运行时使用,例如:磁盘空间为空,一般很少使用

注意:


在logging中,根据等级从低到高的顺序是:

DEBUG < INFO < WARNING < ERROR < CRITICAL

在定义logging的等级时,输出等级如果大于等于定义的等级,最终还是会继续输出,例如:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
logging.debug('n = %d' % n)
logging.warning('n = %d' % n)
logging.error('n = %d' % n)
logging.critical('n = %d' % n)
print(10/n)
#输出
INFO:root:n = 0
WARNING:root:n = 0
ERROR:root:n = 0
CRITICAL:root:n = 0
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 13, in <module>
    print(10/n)
ZeroDivisionError: division by zero
- 可以看到定义的等级是'INFO',其中'DEBUG'等级比'INFO'低,所以'logging.debug('n = %d' % n)'语句没有输出,修改一下代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
logging.basicConfig(level=logging.DEBUG)
s = '0'
n = int(s)
logging.info('n = %d' % n)
logging.debug('n = %d' % n)
logging.warning('n = %d' % n)
logging.error('n = %d' % n)
logging.critical('n = %d' % n)
print(10/n)
#输出
INFO:root:n = 0
DEBUG:root:n = 0
WARNING:root:n = 0
ERROR:root:n = 0
CRITICAL:root:n = 0
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 13, in <module>
    print(10/n)
ZeroDivisionError: division by zero
- 可以看到,因为'DEBUG'等级就是最低的了,所以其他等级的输出都可以正常输出,再次修改代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
logging.basicConfig(level=logging.CRITICAL)
s = '0'
n = int(s)
logging.info('n = %d' % n)
logging.debug('n = %d' % n)
logging.warning('n = %d' % n)
logging.error('n = %d' % n)
logging.critical('n = %d' % n)
print(10/n)
#输出:
CRITICAL:root:n = 0
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 13, in <module>
    print(10/n)
ZeroDivisionError: division by zero
- 这里因为'CRITICAL'等级就是最高的了,所以其他等级的输出信息都无法输出了,现在来看如果不指定等级
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
#logging.basicConfig(level=logging.CRITICAL)
s = '0'
n = int(s)
logging.info('n = %d' % n)
logging.debug('n = %d' % n)
logging.warning('n = %d' % n)
logging.error('n = %d' % n)
logging.critical('n = %d' % n)
print(10/n)
#输出
WARNING:root:n = 0
ERROR:root:n = 0
CRITICAL:root:n = 0
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 13, in <module>
    print(10/n)
ZeroDivisionError: division by zero
- 可以看到,当不指定输出,因为默认等级就是'WARNING'所以'info'和'debug'就无法正常输出了

- pdb


  • 第四种方式就是启动python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态
  • 如果有人用过ansible的话,ansible-playbook --step也可以单步执行剧本
  • 下面来看案例:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
s = '0'
n = int(s)
print(10 / n )
#执行: python -m pdb .py文件名称
PS F:\MY_python\Python> & C:/Users/12488/AppData/Local/Microsoft/WindowsApps/python3.10.exe -m pdb f:/MY_python/Python/test2.py
> f:\my_python\python\test2.py(3)<module>()  #进入调试器
-> s = '0'    #第一行代码
(Pdb) l    #使用 ' l '可以查看当前所有代码 
  1     #!/usr/bin/env python3
  2     # -*- coding: utf-8 -*-
  3  -> s = '0'
  4     n = int(s)
  5     print(10 / n )
[EOF]
(Pdb) n  #使用' n '继续下一步 
> f:\my_python\python\test2.py(4)<module>()
-> n = int(s)
(Pdb) n
> f:\my_python\python\test2.py(5)<module>()
-> print(10 / n )
(Pdb) n
ZeroDivisionError: division by zero
> f:\my_python\python\test2.py(5)<module>()
-> print(10 / n )
(Pdb) p n   #使用' p 变量名 '可以查看当前变量的值
0
(Pdb) p s
'0'
(Pdb) n
ZeroDivisionError: division by zero
> <string>(1)<module>()->None
(Pdb) q   #使用' q '退出程序
PS F:\MY_python\Python> 
  • 使用pdb方法再命令行调试理论上是万能的,但是如果代码有好几千行,使用pdb方法显然就不太可以了


- pdb.set_trace()


这个方法也是使用pdb,但是无需单步执行,我们只需要在代码导入模块import pdb即可,然后再可能会出错的地方放一个pdb.set_trace(),就可以设置一个断点,例如:


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pdb
s = '0'
n = int(s)
pdb.set_trace()   #再运行到这里时会暂停,并且进入pdb调试环境
print(10 / n )
#执行
PS F:\MY_python\Python> & C:/Users/12488/AppData/Local/Microsoft/WindowsApps/python3.10.exe f:/MY_python/Python/test2.py
> f:\my_python\python\test2.py(8)<module>()
-> print(10 / n )
(Pdb) p n  #可以使用' p 变量名 ' 查看值
0
(Pdb) c  #继续运行剩余代码
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 8, in <module>
    print(10 / n )
ZeroDivisionError: division by zero


  • 这样的方式虽然比直接使用pdb效率好一点,但是也是二斤八两


- IDE


  • 除了上面的调试方法,我们还可以直接下载一个支持调试功能的IDE,目前较好的有:VS codePyCharm,我自己使用的就是VS code


四、单元测试


如果你听过测试驱动开发(TDD:Test-Driven Development),那么单元测试就不会陌生


注释:测试驱动开发,英文全称Test-Driven Development,简称TDD,是一种不同于传统 软件开发流程 的新型的开发方法。 它要求在 编写 某个 功能 的 代码 之前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进行。


单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验测试工作的,例如:

- 以'abs()'函数为例,我们来写几个测试用例:
1、输入正数,例如1、1.2、0.99,期待返回值与输入相同
2、输入负数,例如-1、-1.2、-0.99,期待返回值与输入相反
3、输入0,期待返回0
4、输入非数值类型,比如None、列表、字典等,期待抛出错误TypeError

把上述的测试用例放到一个测试模块里,就是一个完整的单元测试


如果单元测试通过,说明测试的函数能够正常工作,如果单元测试不通过,那么就需要看函数是不是又bug,是不是测试条件输入不正确,最终需要使测试单元能够成功通过


单元测试通过后,如果我们对abs()函数做了修改,只需要再跑一遍单元测试,如果通过,说明我们进行的修改没有对函数原有的行为造成影响,反之,测试不通过,就说明我们进行的修改对函数原有的行为造成了影响,这个时候就需要根据需求修改函数或者修改测试


这种以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合我们设计的测试用例,在将来修改时,可以极大程度的保证该模块的行为仍然是正确的


这里不细说了,感兴趣的可以去看一下原文,原文讲述了如何进行单元测试,以及单元测试框架unittest的简单使用


五、文档测试


  • 如果经常阅读Python的官方文档,会看到很多文档都会有示例代码,例如:


>>> import re
>>> m = re.search('(?<=abc)def', 'abcdef') 
>>> m.group(0) 
'def'


在自己动手在交互环境执行时,会发现输出结果是一样的,这些代码与其他说明可以写在注释中,然后又一些工具自动生成文档


既然这些代码本身就可以粘贴出来直接运行,那么是不是也可以自动执行写在注释中的这些代码呢,肯定是可以的,例如:

- 当我们编写注释时,如果写上这样的注释
def abs(n):
    '''
   获取数字绝对值的函数。
    示例:
    >>> abs(1)
    1
    >>> abs(-1)
    1
    >>> abs(0)
    0
    '''
    return n if n >= 0 else (-n)


像上面那样编写注释,无疑是更加明确的告诉函数调用者该函数的期望输入与输出


在Python中,Python内置的文档测试doctest模块可以直接提取注释中的代码并且执行测试


doctest严格按照Python交互式命令行的输入和输出,从而来判断测试结果是否正确,只有测试异常时,可以使用...表示中间一大段无用的输出,现在使用doctest来测试Dict类

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
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(Dict, self).__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
if __name__=='__main__':
    import doctest  #导入模块
    doctest.testmod()
- 没有任何输出,这说明我们编写的doctest运行都是正确的,如果程序有问题,例如修改一下'__getattr__()'方法,再次运行
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
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(Dict, self).__init__(**kw)
    def __getattr__(self, key):
        try:
            pass
            #return self[key]   #注释返回值
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
    def __setattr__(self, key, value):
        self[key] = value
if __name__=='__main__':
    import doctest
    doctest.testmod()
#执行输出:
**********************************************************************
File "f:\MY_python\Python\test2.py", line 9, in __main__.Dict
Failed example:
    d1.x
Expected:
    100
Got nothing
**********************************************************************
File "f:\MY_python\Python\test2.py", line 15, in __main__.Dict
Failed example:
    d2.c
Expected:
    '3'
Got nothing
**********************************************************************
File "f:\MY_python\Python\test2.py", line 21, in __main__.Dict
Failed example:
    d2.empty
Expected:
    Traceback (most recent call last):
        ...
    AttributeError: 'Dict' object has no attribute 'empty'
Got nothing
**********************************************************************
1 items had failures:
   3 of   9 in __main__.Dict
***Test Failed*** 3 failures.


  • 注意

注意上面的最后三行代码,当模块正常导入时,doctest不会被执行,只有在命令行直接运行时,才会执行doctest,所以不用担心dotest会在非测试环境下执行


  • 下面来看一个编写doctest的案例:
# -*- coding: utf-8 -*-
def fact(n):
    '''
    Calculate 1*2*...*n
    >>> fact(1)
    1
    >>> fact(10)
    3628800
    >>> fact(-1)
    Traceback (most recent call last):
      File "/usr/local/lib/python3.9/doctest.py", line 1336, in __run
        exec(compile(example.source, filename, "single",
      File "<doctest __main__.fact[2]>", line 1, in <module>
        fact(-1)
      File "/app/main.py", line 16, in fact
        raise ValueError('valueError!')
    ValueError: valueError!
    '''
    if n < 1:
        raise ValueError('valueError!')
    if n == 1:
        return 1
    return n * fact(n - 1)
if __name__ == '__main__':
    import doctest
    doctest.testmod()
- 执行后没有任何输出,其实只需要看报错信息修改注释即可,例如:
报错信息是这样的:
**********************************************************************
File "/app/main.py", line 10, in __main__.fact
Failed example:
    fact(-1)   #这是执行的语句
Exception raised:     #这个语句块下面的就是输出的结果,这个结果跟注释中写的不一样就会报错,只需要根据这个报错修改代码,或者修改注释即可
    Traceback (most recent call last):
      File "/usr/local/lib/python3.9/doctest.py", line 1336, in __run
        exec(compile(example.source, filename, "single",
      File "<doctest __main__.fact[2]>", line 1, in <module>
        fact(-1)
      File "/app/main.py", line 17, in fact
        raise ValueError('valueError!')
    ValueError: valueError!
**********************************************************************
1 items had failures:
   1 of   3 in __main__.fact
***Test Failed*** 1 failures.
修改代码后:
# -*- coding: utf-8 -*-
def fact(n):
    '''
    Calculate 1*2*...*n
    >>> fact(1)
    1
    >>> fact(10)
    3628800
    >>> fact(-1)
    Traceback (most recent call last):
      File "/usr/local/lib/python3.9/doctest.py", line 1336, in __run
        exec(compile(example.source, filename, "single",
      File "<doctest __main__.fact[2]>", line 1, in <module>
        fact(-1)
      File "/app/main.py", line 17, in fact
        raise ValueError('valueError!')
    ValueError: valueError!
    '''
    if n < 1:
        raise ValueError('valueError!')
    if n == 1:
        return 1
    return n * fact(n - 1)
if __name__ == '__main__':
    import doctest
    doctest.testmod()
#再次执行,发现语句没有输出了                     


目录
相关文章
|
1月前
|
安全 测试技术 网络安全
如何在Python Web开发中进行安全测试?
如何在Python Web开发中进行安全测试?
|
1月前
|
安全 关系型数据库 测试技术
学习Python Web开发的安全测试需要具备哪些知识?
学习Python Web开发的安全测试需要具备哪些知识?
33 4
|
1月前
|
存储 JSON 监控
告别Print,使用IceCream进行高效的Python调试
本文将介绍**IceCream**库,这个专门用于调试的工具显著提升了调试效率,使整个过程更加系统化和规范化。
60 2
告别Print,使用IceCream进行高效的Python调试
|
1月前
|
Java 测试技术 持续交付
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
本文重点讲解如何搭建App自动化测试框架的思路,而非完整源码。主要内容包括实现目的、框架设计、环境依赖和框架的主要组成部分。适用于初学者,旨在帮助其快速掌握App自动化测试的基本技能。文中详细介绍了从需求分析到技术栈选择,再到具体模块的封装与实现,包括登录、截图、日志、测试报告和邮件服务等。同时提供了运行效果的展示,便于理解和实践。
95 4
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
|
18天前
|
敏捷开发 测试技术 持续交付
自动化测试之美:从零开始搭建你的Python测试框架
在软件开发的马拉松赛道上,自动化测试是那个能让你保持节奏、避免跌宕起伏的神奇小助手。本文将带你走进自动化测试的世界,用Python这把钥匙,解锁高效、可靠的测试框架之门。你将学会如何步步为营,构建属于自己的测试庇护所,让代码质量成为晨跑时清新的空气,而不是雾霾中的忧虑。让我们一起摆脱手动测试的繁琐枷锁,拥抱自动化带来的自由吧!
|
23天前
|
存储 算法 C语言
用C语言开发游戏的实践过程,包括选择游戏类型、设计游戏框架、实现图形界面、游戏逻辑、调整游戏难度、添加音效音乐、性能优化、测试调试等内容
本文探讨了用C语言开发游戏的实践过程,包括选择游戏类型、设计游戏框架、实现图形界面、游戏逻辑、调整游戏难度、添加音效音乐、性能优化、测试调试等内容,旨在为开发者提供全面的指导和灵感。
38 2
|
1月前
|
监控 安全 测试技术
如何在实际项目中应用Python Web开发的安全测试知识?
如何在实际项目中应用Python Web开发的安全测试知识?
29 4
|
1月前
|
缓存 测试技术 Apache
告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
53 1
|
1月前
|
Web App开发 测试技术 数据安全/隐私保护
自动化测试的魔法:使用Python进行Web应用测试
【10月更文挑战第32天】本文将带你走进自动化测试的世界,通过Python和Selenium库的力量,展示如何轻松对Web应用进行自动化测试。我们将一起探索编写简单而强大的测试脚本的秘诀,并理解如何利用这些脚本来确保我们的软件质量。无论你是测试新手还是希望提升自动化测试技能的开发者,这篇文章都将为你打开一扇门,让你看到自动化测试不仅可行,而且充满乐趣。
下一篇
DataWorks