文章目录
- 错误处理
- 调试
- 断言assert
- logging
- pdb
- IDE
- 单元测试
- setUp与tearDown
- 单元测试小结
- 文档测试
- 参考网址
错误处理
和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
语句来捕捉各个异常,但是由于捕捉到异常后是从上到下匹配的且Exception
是ZeroDivisionError
的父类,故而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()
函数打印需要查看的变量的值。
但是这种方式仅限于平时学习使用。在工作当中是绝对不允许的。
断言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
类提供了很多内置的条件判断,最常用的就是assertEqual
、assertTrue
以及发生异常时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
即可运行文档测试,如果什么信息都没有就表示文档测试通过。如果出错了就应该有信息(下面的截图中我故意写错来展示错误信息)