Python3学习笔记之-错误、调式、测试(第二篇)
目录
目录
一、调试
1.断言
程序避免不了就是出错,原始的解决方法就是用print()打印出来,但是这种方法不仅麻烦还会增加没用的代码,这个时候就出现了断言(assert),只要可以用print的地方就能够用断言来替代:
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
def main():
foo('0')
assert的意思是,表达式n != 0应该是True,否则根据程序运行的逻辑,后面代码肯定会出错。
如果断言失败,assert语句本身就会抛出AssertionError:
$ python err.py
Traceback (most recent call last):
...
AssertionError: n is zero!
程序中如果到处充斥着assert
,和print()
相比也好不到哪去。不过,启动Python解释器时可以用-O
参数来关闭assert
:
$ python -O err.py
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
注意:断言的开关“-O”是英文大写字母O,不是数字0。
关闭后,你可以把所有的assert
语句当成pass
来看。
2.logging
把print()
替换为logging
是第3种方式,和assert
比,logging
不会抛出错误,而且可以输出到文件:
import logging
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
logging.info()
就可以输出一段文本。运行,发现除了ZeroDivisionError
,没有任何信息。怎么回事?
别急,在import logging
之后添加一行配置再试试:
import logging
logging.basicConfig(level=logging.INFO)
看到输出了:
$ python err.py
INFO:root:n = 0
Traceback (most recent call last):
File "err.py", line 8, in <module>
print(10 / n)
ZeroDivisionError: division by zero
这就是logging
的好处,它允许你指定记录信息的级别,有debug
,info
,warning
,error
等几个级别,当我们指定level=INFO
时,logging.debug
就不起作用了。同理,指定level=WARNING
后,debug
和info
就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
logging
的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。
3.pdb
python的调试器pdb,让程序以单步方式运行,
python -m pdb hello.py
启动-m pdb后,输入(pdb)1可以查看代码,输入(pdb)n可以单步执行代码,输入(pdb)变量名,就可以查看变量,输入(pdb) q结束调试,
它确实可以让程序一步一步运行,然后寻找到哪一步出错,但是如果有几千行代码就太麻烦了。
4.pdb.set_trace()
这个方法也是用pdb,但是不需要单步执行,我们只需要import pdb,然后在一个可能出错的地方放一个pdb.set_trace(),就可以设置一个断点:
# err.py
import pdb
s = '0'
n = int(s)
pdb.set_trace() # 运行到这里会自动暂停
print(10 / n)
运行代码,程序会自动在pdb.set_trace()
暂停并进入pdb调试环境,可以用命令p
查看变量,或者用命令c
继续运行,但是效率也只能说略微提高一点。上面的方法基本上用不到,我们用到最方便快捷的那就是IDE,就是支持调试的编程工具,比如vs code,或者pycharm,但是程序编写久了,logging才会给你带来意想不到的惊喜。
二、单元测试
单元测试是针对程序模块的进行正确性检验的测试工作,测试保证了程序的健壮性,可以让你考虑代码的设计。
1.python测试相关库
- unittest 内置库,模仿PyUnit写的,简洁易用,缺点是比较繁琐。
- nose 测试发现,发现并运行测试。
- pytest 写起来很方便,并且很多知名开源项目在用,推荐。
- mock 替换掉网络调用或者 rpc 请求等
2.pytest进行单元测试
python内置了一个unittest,但是写起来稍微繁琐,比如都要写一个TestCase类,还得用 assertEqual, assertNotEqual等断言方法。 而使用pytest运行测试统一用assert语句就行,兼容unittest,目前很多知名开源项目如PyPy,Sentry也都在用。关于pytest的使用可以参考其官方文档,虽然有很多高级特性,但是掌握其中一小部分基本就够用了。
下面是py.test的基本用法,以常见的两种测试类型(验证返回值和抛出异常)为例:
def add(a, b):
"""return a + b
Args:
a (int): int
b (int): int
Returns:
a + b
Raises:
AssertionError: if a or b is not integer
"""
assert all([isinstance(a, int), isinstance(b, int)])
return a + b
def test_add():
assert add(1, 2) == 3
assert isinstance(add(1, 2) , int)
with pytest.raises(Exception): # test exception
add('1', 2)
这是个脑残示例,不过基本使用就是这么简单。真实场景下远远比这个复杂,甚至有时候构造测试的时间比写业务逻辑的时间还要长。但是再复杂的逻辑也是一点点功能堆积,如果可以确保每一部分都正确,整体上是不会出错的。单元测试同时也提醒我们,函数完成的功能尽可能单一,这样才利于测试。
下面几个是常用的pytest命令:
py.test test_mod.py # run tests in module
py.test somepath # run all tests below somepath
py.test -q test_file_name.py # quite输出
py.test -s test_file_name.py # -s参数可以打印测试代码中的输出,默认不打印,print没结果
py.test test_mod.py::test_func # only run tests that match the "node ID",
py.test test_mod.py::TestClass::test_method # run a single method in
3.测试驱动开发(TDD)的流程
错误:匆匆写代码->实现后print输出看结果->有逻辑或语法错误->修改->继续print输出看结果 循环往复。
采用TDD的流程如下:
TDD三项法则:
- 在编写失败的单元测试之前,不要编写任何产品代码
- 只要有一个单元测试失败了,就不再写测试代码;
- 产品代码能够让当前失败的单元测试成功通过即可,不要多写
三、文档测试
Python中的文档测试doctest非常之简单,就是对注释部分测试,举例:
#coding:UTF-8
import doctest
def ceshi(x):
"""#这是文档测试的内容
>>> ceshi(2)
1
>>> ceshi(3)
0
"""#End
if x%2==0:
return 1
else:
return 0
if __name__ == "__main__":
doctest.testmod(verbose=True)
#doctest.testmod是测试模块,verbose默认是False,意思是出错才用提示;True,对错都有执行结果
下面是运行结果:
Trying:
ceshi(2)
Expecting:
1
ok
Trying:
ceshi(3)
Expecting:
0
ok
1 items had no tests:
__main__
1 items passed all tests:
2 tests in __main__.ceshi
2 tests in 2 items.
2 passed and 0 failed. #显示两个测试都通过了
Test passed.