【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.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM