python中的 错误处理、调试和测试

错误处理

和Java类似,Python提供了一套错误处理机制,语法是 try...except...finally...

可以将你认为会发生错误的代码用try包裹起来并用except捕获指定的错误或异常,最后使用finally执行语句块如发生错误后也要进行资源的回收等。
Python的错误其实也是class,所有的错误类型都继承自BaseException

出错的时候,一定要分析错误的调用栈信息,才能定位错误的位置

可以通过python内置的logging模块来记录异常错误信息如logging.exception(e),这个logging模块后面会有专门的文章来介绍。

我们也可以手动向上层抛出异常,可以通过raise关键字,如抛出属性找不到的异常raise AttributeError("'Model' object has no attribute '%s'" % key)。在except代码块里捕获到异常后我们也可以通过raise将异常原封不动地抛出,也可以抛出自定义错误。

如下图,我们可以看出AttributeError就是最终继承于BaseException
在这里插入图片描述
如下面代码所示。我在main()方法里写了多个except语句来捕捉各个异常,但是由于捕捉到异常后是从上到下匹配的且ExceptionZeroDivisionError的父类,故而except ZeroDivisionError as e:永远也执行不了。
捕捉到异常后,我通过logging模块来记录异常错误信息并用raise将错误信息原样抛出给上层。当然是否需要抛出看当时业务需求。

import logging

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)
        # 用logging模块记录异常
        logging.exception(e)
        raise  # 原样将捕捉到的异常抛出
        # raise ZeroDivisionError("除数为0发生的错误")  # 抛出指定异常
    except ZeroDivisionError as e:
        pass  # 注意这里是永远执行不到的,因为Exception是ZeroDivisionError的父类,except捕捉到异常后从上往下匹配异常
    finally:
        print('finally...')

main()

运行结果如下图所示
在这里插入图片描述

调试

print

最简单粗暴的方式就是使用print()函数打印需要查看的变量的值。
但是这种方式仅限于平时学习使用。在工作当中是绝对不允许的。

断言assert

这个和Java里的断言非常类似。都是用于判断一个表达式,在表达式条件为 false 的时候触发异常。
语法是 assert expression [, arguments],等价于如下代码。当然参数是可选的,不过一般最好添加参数,可以给人提示错误信息。

if not expression:
    raise AssertionError(arguments)

如下面计算一个数的平方,用assert判断必须是数字,否则就会抛出异常

# 计算一个数的平方
def square(num):
    assert isinstance(num, (int,float)), "{} 不是数字类型".format(num)
    print("the square of {} is {}".format(num, num**2))

square(10)
square("10")

运行结果如下
在这里插入图片描述
当然如果程序中导出都充斥着assert也非常不好,一般断言都是在测试代码功能时发挥作用的。
不过我们可以在启动python解释器时通过参数-O来关闭assert,此时assert语句可以看做pass

如下所示。关闭assert后异常信息就不同了。注意参数是大写的字母O不是数字零
在这里插入图片描述

logging

必须配置logging.basicConfig(level=logging.INFO)设定日志级别,否则会信息是无法打印的。
更加详细的配置信息后续再写博客介绍。

import logging
logging.basicConfig(level=logging.INFO)
logging.info("测试中logging模块")

在这里插入图片描述

pdb

启动Python的调试器pdb(启动python解释器时添加参数-m pdb),让程序以单步方式运行,可以随时查看运行状态。
我们也可以在代码中导入pdb并在需要调试的地方写上pdb.set_trace(),这样就可以跳跃进行调试。

输入 l 查看代码。字母L的小写。
输入 n 执行下一行代码。单步执行。
输入 p 变量名 查看变量。
输入 c 继续运行到下一个设置pdb.set_trace()的代码。
输入 q 退出。

# -*- coding: UTF-8 -*-

import pdb
import logging
logging.basicConfig(level=logging.INFO)
logging.info("测试中logging模块")


# 计算一个数的平方
def square(num):
    assert isinstance(num, (int,float)), "{} 不是数字类型".format(num)
    print("the square of {} is {}".format(num, num**2))
    return num**2

pdb.set_trace()
res = square(10)
print(1+2)
pdb.set_trace()
print(3+4)
logging.info("finish....")

运行结果如下
在这里插入图片描述

IDE

当然调试最好的方式还是IDE编辑器。如PyCharm等。
不过也最好结合logging去调试。

单元测试

为了测试我们的代码功能是否正常,可以写一组测试用例放在一个模块,这就叫做一个完整的单元测试。
当后续我们的代码修改后,依旧可以运行这个单元测试保证我们的代码在功能上是正常的。

python内置模块unittest可以让我们去写单元测试。
首先我们需要编写一个测试类,该测试类需要继承unittest.TestCase。该测试类下以test开头的方法就是测试方法,没有以test开头的方法就不是测试方法,在测试的时候不会被执行。

unittest.TestCase类提供了很多内置的条件判断,最常用的就是assertEqualassertTrue以及发生异常时with self.assertRaises(AttributeError):

有两种方式运行单元测试.

  • 在测试代码里添加unittest.main(),然后像运行普通python脚本一样运行该单元测试文件。
  • 命令行输入 python -m unittest 文件名 来执行单元测试。这种方式更加推荐常用,因为这样可以一次批量运行很多单元测试。

下面是我的mydict.py文件,自定义一个字典类

# -*- coding: UTF-8 -*-


class Dict(dict):

    def __init__(self, **kw):
        super().__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError("'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

下面是我的单元测试文件mydict_test.py

# -*- coding: UTF-8 -*-


import unittest

from mydict 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.key, '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(KeyError):
            value = d['empty']

    def test_attrerror(self):
        d = Dict()
        with self.assertRaises(AttributeError):
            value = d.empt

    def abc(self):
        10/0

    def test_abc(self):
        self.assertEqual(1+2, 3)

# 在用python -m unittest 文件名 执行单元测试时不需要下面两行代码
if __name__ == '__main__':
    unittest.main()

采用第一种方式运行单元测试结果如下图所示
在这里插入图片描述
采用第二种方式运行单元测试结果如下图所示
在这里插入图片描述

setUp与tearDown

可以在单元测试中编写两个特殊的setUp()tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行
设想你的测试需要启动一个数据库,这时,就可以在setUp()方法中连接数据库,在tearDown()方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码。

class TestDict(unittest.TestCase):

    def setUp(self):
        print("setUp ... ")

    def tearDown(self):
        print("tearDown ... ")

在这里插入图片描述

单元测试小结

单元测试可以有效地测试某个程序模块的行为,是未来重构代码的信心保证。

单元测试的测试用例要覆盖常用的输入组合、边界条件和异常。

单元测试代码要非常简单,如果测试代码太复杂,那么测试代码本身就可能有bug。

单元测试通过了并不意味着程序就没有bug了,但是不通过程序肯定有bug。

文档测试

函数或者模块里的文档注释有一些样例代码,文档测试就可以将这些样例代码抽取出来放在交互式的python环境下运行看是否和预期结果一致。
如果是遇到异常的话应该在文档注释里这么写,其中用...表示一大串异常信息。

>>> fact("abc")
Traceback (most recent call last):
...
TypeError: 必须传入整数 参数是abc

下面代码就展示了如何用文档测试测试自己写的fact方法的正确性。
如何运行文档测试呢?可以参考下面代码的最后三行,导入了doctest模块。

# -*- coding: UTF-8 -*-


def fact(n):
    '''
    计算n对应的阶乘
    >>> fact(1)
    1
    >>> fact(5)
    120
    >>> fact("abc")
    Traceback (most recent call last):
    ...
    TypeError: 必须传入整数 参数是abc
    >>> fact(-1)
    Traceback (most recent call last):
     ...
    ValueError: 不能小于1
    >>>
    :param
    :return
    '''
    if not isinstance(n, int):
        raise TypeError("必须传入整数 参数是{}".format(n))
    if n < 1:
        raise ValueError("不能小于1")
    if n == 1:
        return 1
    return n * fact(n-1)


if __name__=='__main__':
    import doctest
    doctest.testmod()

在pycharm中鼠标点击到文档测试代码部分然后右键即可运行文档测试。
在这里插入图片描述

在命令行中输入python test.py即可运行文档测试,如果什么信息都没有就表示文档测试通过。如果出错了就应该有信息(下面的截图中我故意写错来展示错误信息)
在这里插入图片描述

参考网址

廖雪峰老师的python教程

发布了31 篇原创文章 · 获赞 0 · 访问量 1791

猜你喜欢

转载自blog.csdn.net/qq_23120963/article/details/105463162