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

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

一、概述


  • 在程序运行中,总会遇到各种各样的错误,有的错误是在程序编写时有问题造成的,例如:


本来应该输出整数却输出了字符串


这种错误通常称之为BUG,BUG是必须要修复的


  • 除了在编写时导致的错误,还有的错误是在用户输入是造成的,例如:


程序让用户输入一个邮箱地址,而用户输入了一串空字符串


这样的错误我们可以通过检查用户输入的数据来避免


  • 还有一种错误,是完全无法在程序运行过程中预测的,例如:


写入磁盘的时候,磁盘空间满了,从网络抓取数据时,网络断开了


这种错误也叫做异常,在程序中通常是必须要做处理的,否则,程序会因为各种问题中止退出


而在Python中,Python内置了一套异常处理机制,帮助我们进行错误处理,除此之外,我们也需要跟踪程序的执行,查看变量的值是否正确,而这个过程就叫做调试,Python中的pdb可以让我们以单步方式执行代码


最后,编写测试也很重要,有了良好的测试,就可以在程序修改后反复运行,确保程序输出符合我们的要求


二、错误处理


在程序运行过程中,如果发生了错误,可以实现返回一个错误代码,这样就可以知道是否存在错误以及出现错误的原因


在操作系统提供的调用中,返回错误代码是非常常见的,例如函数open(),这个函数可以打开一个文件,成功会返回一个文件描述符即一个整数,失败则会返回-1


但是使用上述的错误码来表示代码是否出现错误是非常不方便的,这是因为函数本身返回的正常结果会和错误码混在一起,这样的结果就是我们需要写大量的代码区判断是否出现错误,例如:


- 创建一个'test_1'函数,模仿传入参数不正确时,返回不一样的数据,然后使用'test_2'函数进行判断
# -*- coding: utf-8 -*-
def test_1(x):   
    if x > 1:
        return 1
    else:
        return (-1)
def test_2():
    a = test_1(2)
    if a == 1:
        return 'ok'
    else:
        return 'error'
print(test_2())
#输出:
ok
- 更换一下参数
# -*- coding: utf-8 -*-
def test_1(x):
    if x > 1:
        return 1
    else:
        return (-1)
def test_2():
    a = test_1(0)
    if a == 1:
        return 'ok'
    else:
        return 'error'
print(test_2())
#输出:
error


  • 上面只是模拟了一下,而高级语言通常都内置了一套try...except...finally...错误处理机制,Python当然也有


- try


  • 下面来看一下关于try的案例,从而了解try的机制:


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
try:
    print('try...')
    r = 10 / 0    #10/0会产生一个除法运算错误
    print('result:', r)
except ZeroDivisionError as e:  #这里的 ZeroDivisionError是报错类型
    print('except:', e)
finally:
    print('finally...')
print('END')
- 当我们认为某些代码可能会出错时,可以使用'try'来运行这段代码,如果执行的确出错了,那么后续的代码将不会执行,而是直接跳转到错误处理代码,即'except'语句块,在执行完'except'之后,如果还有'finally'语句块的话,那么就执行'finally'语句块的代码,至此执行完成
- 需要注意的是,如果'try'语句块没有发生错误,那么下一步会跳转至'finally'语句块
- 所以当try发生错误时的执行顺序是'try——>except——>finally'
- 当try没有发生错误的执行顺序是'try——>finally'
- 10 / 0 注释:
————————————————
>>> 10/0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero  #可以发现这个报错信息和输出的报错信息是相同的
————————————————
#输出:
try...
except: division by zero
finally...
END
- 从输出可以看到,在错误发生时,'r = 10 / 0 '的后面'print('result:', r)'并没有执行,而是去了'except'语句块执行了'print('except:', e)',然后到'finally'语句块执行了'print('finally...')',最后执行了'print('END')',因为'print('END')'并不包含在'try'语句块中


然后我们修改一下代码,使r变量可以成功赋值:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
try:
    print('try...')
    r = 10 / 2    
    print('result:', r)
except ZeroDivisionError as e:
    print('except:', e)
finally:
    print('finally...')
print('END')
#输出:
try...
result: 5.0
finally...
END
- 可以看到当'try'语句块没有发生错误时,执行'try'语句块后,会跳转到'finally'语句块
- 注意,'finally'语句块可以不加

看过上面的案例后,有一步except ZeroDivisionError as e:,我们发现了10 / 0 的错误类型和except ZeroDivisionError as e:这里的错误类型是一样的,其实错误类型还有很多种,这里只是捕获了一种错误类型


而想要处理不同的错误类型,就需要创建不同的except语句块来捕获,例如:

- 修改代码,可以添加多个错误捕获,例如'ValueError'
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
try:
    print('try>>>>')
    r = 10 / int('aaa')
    print('result: %s' % r)
except ValueError as a:
    print('ValueError: %s' % a)
except ZeroDivisionError as b:
    print('ZeroDivisionError: %s' % b)
finally:
    print('finally>>>>')
print('END>>>>')
#输出:
try>>>>
ValueError: invalid literal for int() with base 10: 'aaa'
finally>>>>
END>>>>
- 可以发现写了两个'except'语句块,最后只输出了'ValueError'语句块的内容,这是因为在'int('aaa')'的时候就报错了,修改一下
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
try:
    print('try>>>>')
    r = 10 / int('0')
    print('result: %s' % r)
except ValueError as a:
    print('ValueError: %s' % a)
except ZeroDivisionError as b:
    print('ZeroDivisionError: %s' % b)
finally:
    print('finally>>>>')
print('END>>>>')
#输出:
try>>>>
ZeroDivisionError: division by zero
finally>>>>
END>>>>
- 这里修改成'0',最后的输出变成了'ZeroDivisionError'语句块的代码

除了上面说的,如果没有try语句块没有发生错误时,还可以在except语句块后面加一个else语句块,当没有错误发生时,会执行else语句块,例如:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
try:
    print('try>>>>')
    r = 10 / int('2')
    print('result: %s' % r)
except ValueError as a:
    print('ValueError: %s' % a)
except ZeroDivisionError as b:
    print('ZeroDivisionError: %s' % b)
else:
    print('no error')
print('END>>>>')
#输出:
try>>>>
result: 5.0
no error
END>>>>

Python的错误其实也是类,所有的错误类型都继承自BaseException,所以在使用except时需要注意,它不但会捕获该类型的错误,还会捕获其子类,例如:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
try:
    print('try>>>>')
    r = 10 / int('aaa')
    print('result: %s' % r)
except ValueError as a:
    print('ValueError: %s' % a)
except UnicodeError as b:
    print('UnicodeError: %s' % b)
#输出:
try>>>>
ValueError: invalid literal for int() with base 10: 'aaa'
- 这里的'UnicodeError'语句块永远不会执行,因为'UnicodeError'是'ValueError'的子类,'UnicodeError'的错误会被'ValueError'捕获


Python的所有错误都是从BaseException类派生的,常见的错误类型和继承关系如下:

官网:https://docs.python.org/3/library/exceptions.html#exception-hierarchy
BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      |    +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- EncodingWarning
           +-- ResourceWarning

除了上面说的,使用try...except捕获错误还有一个好处,就是可以跨越多层调用,例如:

- 使用'main()'调用'bar()','bar()'调用'foo()',这时只要'main()'捕获到错误了,就可以进行错误处理
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def foo(s):
    return 10 / int(s)
def bar(s):
    return foo(s) * 2
def main():
    try:
        bar('0')
    except Exception as e:
        print('Error:', e)
    finally:
        print('finally...')
main()
#输出:
Error: division by zero
finally...


  • 利用这个特性,我们不需要在每个可能出错的地方捕获错误,我们只需要在合适的层次捕获错误即可,这样就可以减少try...except...finally的数量


- 调用栈


  • 如果错误没有被捕获,那么就会一直往上抛,最后被Python解释器捕获,例如:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def foo(s):
    return 10 / int(s)
def bar(a):
    return foo(a) * 2
def main():
    bar('0')
main()
#输出:
Traceback (most recent call last):
  File "c:\Users\12488\Desktop\python\pachong.py", line 12, in <module>
    main()
  File "c:\Users\12488\Desktop\python\pachong.py", line 10, in main
    bar('0')
  File "c:\Users\12488\Desktop\python\pachong.py", line 7, in bar
    return foo(a) * 2
  File "c:\Users\12488\Desktop\python\pachong.py", line 4, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero

出错并不可怕,可怕的是不知道是哪里出错了,而解读错误信息是定位错误的关键步骤,错误信息从上往下看可以得到整个错误的调用函数链,下面我们来解析一些错误信息:


错误信息:


Traceback (most recent call last):
  File "c:\Users\12488\Desktop\python\pachong.py", line 12, in <module>
    main()
  File "c:\Users\12488\Desktop\python\pachong.py", line 10, in main
    bar('0')
  File "c:\Users\12488\Desktop\python\pachong.py", line 7, in bar
    return foo(a) * 2
  File "c:\Users\12488\Desktop\python\pachong.py", line 4, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero

第1行:


Traceback (most recent call last):
- 错误的跟踪信息

第2—3行:


File "c:\Users\12488\Desktop\python\pachong.py", line 12, in <module>
    main()
- 调用'main()'函数出错了,在代码的12行,但原因是第10行


第4—5行:


File "c:\Users\12488\Desktop\python\pachong.py", line 10, in main
    bar('0')
- 调用'bar('0')'时出错了,在代码的第10行,但原因是第7行


第6—7行:


  File "c:\Users\12488\Desktop\python\pachong.py", line 7, in bar
    return foo(a) * 2
- 原因是因为'return foo(a) * 2'这个语句出错了,但这也不是错误源头

第8—10行:

  File "c:\Users\12488\Desktop\python\pachong.py", line 4, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero
- 原因是'return 10 / int(s)'这个语句,这是错误的源头,因为下面输出了'ZeroDivisionError: division by zero'

注意:当程序出错的时候,一定要分析错误的调用栈信息,这样可以帮助我们快速的定位错误的位置


- 记录错误


  • 如果不捕获错误,自然可以让Python解释器输出错误堆栈,但是这样的话,程序也会被结束
  • 我们既然能捕获错误,其实也可以在程序继续执行的情况下,把错误堆栈打印出来,然后分析错误原因
  • Python内置的logging模块就可以记录错误信息,下面来看案例:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
def foo(s):
    return 10 / int(s)
def bar(a):
    return foo(a) * 2
def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)
main()
print('END>>>')
#输出:
ERROR:root:division by zero
Traceback (most recent call last):
  File "c:\Users\12488\Desktop\python\pachong.py", line 13, in main
    bar('0')
  File "c:\Users\12488\Desktop\python\pachong.py", line 9, in bar
    return foo(a) * 2
  File "c:\Users\12488\Desktop\python\pachong.py", line 6, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero
END>>>
- 可以看到同样是出错,但是在程序打印完报错信息后,最后的'print('END>>>')'语句还是正常执行了
  • 通过配置,logging模块还可以把错误记录到日志文件中,方便进行分析


- 抛出错误


因为错误是类,所以其实捕获一个错误就是捕获到该类的一个实例


错误并不是凭空出现的,而是有意创建并抛出的,Python的内置函数会抛出很多类型的错误,同样,我们自己编写的函数也可以抛出错误


如果要抛出错误,首先要根据需求,可以定义一个错误的类,选择好继承关系,然后使用raise语句抛出错误,例如:


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class FooError(ValueError):
    pass
def test_1(s):
    num = int(s)
    if num == 0 :
        raise FooError('value is  %s !!!' % num)
    return print(10 / num)
test_1('0')
#输出:
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 13, in <module>
    test_1('0')
  File "f:\MY_python\Python\test2.py", line 10, in test_1
    raise FooError('value is  %s !!!' % num)
__main__.FooError: value is  0 !!!
- 可以看到会抛出我们自定义的错误,现在来修改传入的参数
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class FooError(ValueError):
    pass
def test_1(s):
    num = int(s)
    if num == 0 :
        raise FooError('value is  %s !!!' % num)
    return print(10 / num)
test_1('2')
#输出
5.0
  • 从上面的案例可以看到,代码执行后会抛出我们自定义的错误
  • 一般来说只有在必要的时候才会自定义我们自己的错误类型,通常都是选择Python已有的内置错误类型
  • 还有一种处理错误的方法,下面来看案例:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def foo(s):
    num = int(s)
    if num == 0 :
        raise ValueError('value is %s !!' % num)
    return print(10 / num)
def bar():
    try:
        foo('0')
    except ValueError as e:
        print('ValueError')
        raise
bar()
#输出:
ValueError
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 16, in <module>
    bar()
  File "f:\MY_python\Python\test2.py", line 11, in bar
    foo('0')
  File "f:\MY_python\Python\test2.py", line 6, in foo
    raise ValueError('value is %s !!' % num)
ValueError: value is 0 !!

可以看到,在bar()函数中,已经捕获了错误,输了ValueError,但是在后面又通过raise语句把foo()函数的错误抛出去了,这样的处理方法非常常见


捕获错误的目的只是记录一下,方便后续解决错误,但是,由于当前函数不知道该怎么处理该错误,所以,最恰当的方式就是把错误往上面抛,让上一层层调用者去处理,最终让顶层调用者处理


raise语句如果不带参数,就会把当前错误原因抛出


在except中,使用raise抛出一个错误,还可以把一种类型的错误转换成另一种类型,例如:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
try:
    10 /0 
except ZeroDivisionError:
    raise ValueError('error!!!')
#输出:
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 4, in <module>
    10 /0
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "f:\MY_python\Python\test2.py", line 6, in <module>
    raise ValueError('error!!!')
ValueError: error!!!
- 可以看到'except'使用的错误类型是'ZeroDivisionError',但是最终抛出的错误类型是'ValueError'

注意:


Python内置的try...except...finally用来处理错误十分方便。但是在出错时,会分析错误信息并定位错误发生的代码位置才是最关键也是最重要的


在编写程序时,可以使程序主动抛出错误,让调用者来处理相应的错误。但是,应该在文档中写清楚可能会抛出哪些错误,以及错误产生的原因

目录
相关文章
|
1月前
|
Python
Python中 If语句条件测试
Python中 If语句条件测试
20 1
|
1天前
|
前端开发 测试技术 C++
Python自动化测试面试:unittest、pytest与Selenium详解
【4月更文挑战第19天】本文聚焦Python自动化测试面试,重点讨论unittest、pytest和Selenium三大框架。unittest涉及断言、TestSuite和覆盖率报告;易错点包括测试代码冗余和异常处理。pytest涵盖fixtures、参数化测试和插件系统,要注意避免过度依赖unittest特性。Selenium的核心是WebDriver操作、等待策略和测试报告生成,强调智能等待和元素定位策略。掌握这些关键点将有助于提升面试表现。
6 0
|
1天前
|
XML Web App开发 测试技术
python的Web自动化测试
【4月更文挑战第16天】Python在Web自动化测试中广泛应用,借助Selenium(支持多浏览器交互)、BeautifulSoup(解析HTML/XML)、Requests(发送HTTP请求)和Unittest(测试框架)等工具。测试步骤包括环境搭建、编写测试用例、初始化浏览器、访问页面、操作元素、验证结果、关闭浏览器及运行报告。注意浏览器兼容性、动态内容处理和错误处理。这些组合能提升测试效率和质量。
11 6
|
1天前
|
测试技术 持续交付 数据库
python集成测试
【4月更文挑战第16天】在Python集成测试中,确保模块间正确交互是关键。选择合适的测试框架如`unittest`或`pytest`,定义全面的测试用例,编写测试代码并设置类似生产环境的测试环境。执行测试后分析修复问题,将测试整合到持续集成流程,以尽早发现并解决问题。例如,使用`pytest`,我们可以创建测试用例验证不同模块间的功能是否按预期协同工作。
8 2
|
4天前
|
存储 API Python
python之代理ip的配置与调试
python之代理ip的配置与调试
|
9天前
|
Web App开发 测试技术 网络安全
|
14天前
|
JSON 测试技术 持续交付
自动化测试与脚本编写:Python实践指南
【4月更文挑战第9天】本文探讨了Python在自动化测试中的应用,强调其作为热门选择的原因。Python拥有丰富的测试框架(如unittest、pytest、nose)以支持自动化测试,简化测试用例的编写与维护。示例展示了使用unittest进行单元测试的基本步骤。此外,Python还适用于集成测试、系统测试等,提供模拟外部系统行为的工具。在脚本编写实践中,Python的灵活语法和强大库(如os、shutil、sqlite3、json)助力执行复杂测试任务。同时,Python支持并发、分布式执行及与Jenkins、Travis CI等持续集成工具的集成,提升测试效率和质量。
|
16天前
|
监控 物联网 Linux
python测试串口最大通信速率
【4月更文挑战第5天】
|
18天前
|
jenkins 测试技术 持续交付
软件测试|docker搭建Jenkins+Python+allure自动化测试环境
通过以上步骤,你可以在Docker中搭建起Jenkins自动化测试环境,实现Python测试的自动化执行和Allure报告生成。 买CN2云服务器,免备案服务器,高防服务器,就选蓝易云。百度搜索:蓝易云
38 6
|
1月前
|
Web App开发 前端开发 JavaScript
Python Selenium是一个强大的自动化测试工具
Python Selenium是一个强大的自动化测试工具