【Python】错误、调试和测试
小结:遇到错误怎么办?
1、不理它。
2、捕获它,再抛出。
3、捕获并处理错误。
raise
不懂如何处理错误就直接抛出(raise),交由合适的层次处理,有时候需要自定义错误,但是通常使用Python内置的错误就可以了:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # 自定义的异常 class MyError(Exception): pass # 抛出异常的函数 def f(): raise MyError('This is my error!') # 不处理,继续抛 def f2(): f() # 不处理,继续抛 def f3(): f2() print('hi1') # 被打印 f3() print('hi2') # 不会被打印,因为程序因为出错而终止了
处理机的调用和函数调用的方向刚好相反,因为最终都没有人来处理这个错误,所以错误被一直上抛,直到被Python解释器捕获:
hi1 Traceback (most recent call last): File "D:\labs\test.py", line 21, in <module> File "D:\labs\test.py", line 18, in f3 File "D:\labs\test.py", line 14, in f2 File "D:\labs\test.py", line 10, in f __main__.MyError: This is my error!
不懂如何处理错误的第二种方式是记录一下再继续抛出,这需要用到“try...except”:
# 自定义的异常 class MyError(Exception): pass # 抛出异常的函数 def f(): raise MyError('This is my error!') # 不处理,继续抛 def f2(): f() # 不处理,继续抛 def f3(): f2() print('hi1') # 被打印 try: f3() except Exception as e: print('...mark') raise # 原样抛出 finally: print('b') print('hi2') # 错误没被处理了,程序不再执行
输出:
hi1 ...mark b Traceback (most recent call last): File "D:\labs\test.py", line 23, in <module> File "D:\labs\test.py", line 18, in f3 File "D:\labs\test.py", line 14, in f2 File "D:\labs\test.py", line 10, in f __main__.MyError: This is my error!
(可以观察到finally是无论如何都会在最后被执行的!)
try...except
错误一般由底层代码抛出,通常是“他人的代码”,所以更经常写的是try...except别人抛出的错误。
那么如何自己来处理错误呢?当位于合适的层次的时候我们用“try...except”来处理错误:
print('hi1') # 被打印 try: f3() except Exception as e: print('a:', e) finally: print('b') print('hi2') # 错误被处理了,程序继续执行
输出情况:
hi1 a: This is my error! b hi2
测试证明except 父类错误就可以捕获子类错误。
在try的内部一但raise了错误,如果有except语句将其捕获,那么try语句块的剩余语句是不会被执行的,因此try语句块要设定合适的范围,而不是一次try大量的语块:
class Error1(Exception): pass try: print('1') raise Error1('Error1') print('2') # 不被执行 print('3') # 不被执行 except Exception as e: print(e) print('continue execute')
1 Error1 continue execute
对于捕获多个错误的情况:
并不是多个错误都会被捕获,因为一旦raise了第一个错误,try语句块的程序就不会再继续执行了,所以同时抛出多个错误的情况是不存在的,书写多个except语句只是为了逐级排查最终捕获一个错误!
Python的except语句还可以加上else,如果没有错误将会执行else内的语句。
logging
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import logging class Error1(Exception): pass try: print('1') raise Error1('I make a mistake') print('2') # 不被执行 print('3') # 不被执行 except Exception as e: print('4') logging.exception(e) print('5') print('continue execute')
1 4 ERROR:root:I make a mistake Traceback (most recent call last): File "test.py", line 10, in <module> raise Error1('I make a mistake') Error1: I make a mistake 5 continue execute
今天遇到了一个问题卡了好一会儿:TabError: inconsistent use of tabs and spaces in indentation。原因是混用了空格和tab(可能是copy代码带来的)。最后删掉重输了一遍就解决了。
debug的方法:
1、print
2、断言:print的升级版。
3、logging:既可以输出到console,也可以输出到文件、不打断程序执行。
在使用logging之前还需要在程序头添加一行配置:
import logging logging.basicConfig(level=logging.WARNING)
level指定了记录信息的级别,分为DEBUG、INFO、WARNING、ERROR。当level指定为DEBUG,记录debug及以上的logging信息,当level指定为INFO,记录info及以上的logging信息,以此类推:
import logging logging.basicConfig(level=logging.DEBUG) print(1) logging.debug('debug:hi') logging.info('info:hi') logging.warning('warning:hi') logging.error('error') print(2)
输出:
1
DEBUG:root:debug:hi
INFO:root:info:hi
WARNING:root:warning:hi
ERROR:root:error
2
继续修改level的等级:
logging.basicConfig(level=logging.INFO)
1
INFO:root:info:hi
WARNING:root:warning:hi
ERROR:root:error
2
...
logging.basicConfig(level=logging.WARNING)
1
WARNING:root:warning:hi
ERROR:root:error
2
...
logging.basicConfig(level=logging.ERROR)
1
ERROR:root:error
2
4、pdb
在命令行下开启dubug模式(参数要加在文件名前面) :
$ python -m pdb test.py
输入n单步执行代码:
$ python -m pdb test.py > d:\labs\test.py(3)<module>() -> n = 0 (Pdb) n > d:\labs\test.py(4)<module>() -> print(1) (Pdb) n 1 > d:\labs\test.py(5)<module>() -> n = n + 1 (Pdb)
随时可以用 p 变量名查看变量:
> $ test.py(9)<module>() -> n = n + 1 (Pdb) p n 2
使用q来退出。
最后,从某处开始debug的方法:
import pdb print(1) print(2) pdb.set_trace() # 从这里开始进入debug模式。 print(3) print(4) print(5)
直接运行.py程序就可以了。
ps:一直执行n命令将循环运行程序。
Q1:什么是单元测试?
Q2:如何进行单元测试?
A1:单元测试是指对一个模块、一个函数或者一个类来进行正确性检验的测试。
A2:例如我要检测自己写的sum()函数好不好用。
def sum(x, y): return x + y
1、首先需要编写测试用例,这需要用到Python自带的unittest模块:
import unittest from test import sum
class TestSum(unittest.TestCase): def test_func(self): self.assertEqual(sum(1, 2), 3) self.assertEqual(sum(1, -2), -1) self.assertEqual(sum(100, -2), 98) self.assertEqual(sum(1001, 110), 1111)
assertEqual是用于判定两个参数是否相等的函数,unittest模块中还有很多用于检测正确性的函数。 凡是test_xxx在测试的时候都会被运行。
import unittest from test import sum class TestSum(unittest.TestCase): def test_func(self): self.assertEqual(sum(1, 2), 3) self.assertEqual(sum(1, -2), 0) self.assertEqual(sum(100, -2), 98) self.assertEqual(sum(1001, 110), 1111) def test_func2(self): self.assertEqual(sum(1, 2), 3) self.assertEqual(sum(1, -2), 0) self.assertEqual(sum(100, -2), 90) self.assertEqual(sum(1001, 110), 1111) def test_func3(self): self.assertEqual(sum(1, 2), 3) self.assertEqual(sum(1, -2), -1) self.assertEqual(sum(100, -2), 98) self.assertEqual(sum(1001, 110), 1111) def test_func4(self): print('hi~') print('#$@#%@#$^%%#$^@@#$^&*')
2、运行测试用例:
$ python -m unittest sum_test.py . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
$ python -m unittest sum_test.py FF.hi~ #$@#%@#$^%%#$^@@#$^&* . ====================================================================== FAIL: test_func (sum_test.TestSum) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:\labs\sum_test.py", line 9, in test_func self.assertEqual(sum(1, -2), 0) AssertionError: -1 != 0 ====================================================================== FAIL: test_func2 (sum_test.TestSum) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:\labs\sum_test.py", line 15, in test_func2 self.assertEqual(sum(1, -2), 0) AssertionError: -1 != 0 ---------------------------------------------------------------------- Ran 4 tests in 0.000s FAILED (failures=2)
有时候可能会期待是不是抛出一些错误,可以这么写:
def test_attrerror(self): d = Dict() with self.assertRaises(AttributeError): value = d.empty # raise AttributeError()
在测试用例中还可以添加setUp和tearDown函数,这两个函数分别在测试开始时和测试结束时被调用。
1、搭建测试环境。
def sum(x, y): ''' test my sum funtion ''' return x + y if __name__ == '__main__': import doctest doctest.testmod()
$ python test.py # 无输出
2、编写测试代码
def sum(x, y): ''' test my sum funtion >>> sum(2, 3) 5 >>> sum(1, 9) 10 ''' return x + y if __name__ == '__main__': import doctest doctest.testmod()
如果代码测试无误就不会有输出。
如果有错则有类似输出如下:
$ python test.py ********************************************************************** File "test.py", line 8, in __main__.sum Failed example: sum(1, 9) Expected: 9 Got: 10 ********************************************************************** 1 items had failures: 1 of 2 in __main__.sum ***Test Failed*** 1 failures.