天天看點

pytest架構進階自學系列 | 中斷調試及錯誤處理

作者:熱愛程式設計的通信人

書籍來源:房荔枝 梁麗麗《pytest架構與自動化測試應用》

一邊學習一邊整理老師的課程内容及實驗筆記,并與大家分享,侵權即删,謝謝支援!

附上彙總貼:pytest架構進階自學系列 | 彙總_熱愛程式設計的通信人的部落格-CSDN部落格

失敗時加載PDB環境

在工具欄中會有一個小蟲子的圖示,一般筆者在調試時使用這個檢視變量等的情況。PDB(Python Debugger)是Python内建的調試器,如果習慣使用它也是一個不錯的選擇。pytest允許通過以下指令在執行失敗時進入這個調試器模式,代碼如下:

pytest架構進階自學系列 | 中斷調試及錯誤處理

舉例說明:

pytest會在測試用例失敗(或者Ctrl+C)時,調用這個調試器,可以通路測試用例的本地變量x。失敗的資訊存儲在sys.last_value、sys.last_type、sys.last_traceback變量中,可以在互動環境中通路它們。使用exit指令,即可退出PDB環境。

代碼如下:

def test_fail():
    x = 1
    assert x == 0           

執行pytest --pdb test_pdb.py,結果如下:

PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2> pytest --pdb .\test_pdb.py
========================================================================================================= test session starts =========================================================================================================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2, inifile: pytest.ini
plugins: allure-pytest-2.13.2
collected 1 item                                                                                                                                                                                                                       

test_pdb.py F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 

    def test_fail():
        x = 1
>       assert x == 0
E       assert 1 == 0

test_pdb.py:3: AssertionError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PDB post_mortem (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
> d:\synologydrive\codelearning\win\pytest-book\src\chapter-2\test_pdb.py(3)test_fail()
-> assert x == 0
(Pdb) import sys
(Pdb) sys.last_value    
AssertionError('assert 1 == 0')
(Pdb) sys.last_type
<class 'AssertionError'>
(Pdb) sys.last_traceback
<traceback object at 0x00000157D9A21688>
(Pdb) exit


======================================================================================================= short test summary info ======================================================================================================= 
FAILED test_pdb.py::test_fail - assert 1 == 0
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! _pytest.outcomes.Exit: Quitting debugger !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
========================================================================================================= 1 failed in 56.14s ========================================================================================================== 
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2> 
           

開始執行時就加載PDB環境

通過以下指令,pytest允許在每個測試用例開始執行時就加載PDB環境:

pytest架構進階自學系列 | 中斷調試及錯誤處理

設定斷點

通常可以通過在代碼行号附近單擊具體行設定一個斷點,在單擊後運作會停下來。如圖所示。

pytest架構進階自學系列 | 中斷調試及錯誤處理

也可以在測試用例代碼中添加import pdb;pdb.set_trace(),當其被調用時,pytest會停止這條用例的輸出,其他用例不受影響。通過continue指令,退出PDB環境,并繼續執行用例。

代碼如下:

def test_fail():
    x = 1
    import pdb
    pdb.set_trace()
    assert x == 0           

執行結果如下,在進行(Pdb)後,輸入x,傳回1。輸入import sys,無傳回。輸入sys.path,傳回目前系統路徑。輸入continue,用例接着運作,輸出結果如下。

D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2\test_pdb.py 
Testing started at 21:07 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2\test_pdb.py in D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2, inifile: pytest.ini
plugins: allure-pytest-2.13.2, collect-formatter2-0.1.3
collecting ... collected 1 item

test_pdb.py::test_fail 
>>>>>>>>>>>>>>>>>>> PDB set_trace (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>
> d:\synologydrive\codelearning\win\pytest-book\src\chapter-2\test_pdb.py(5)test_fail()
-> assert x == 0
(Pdb) 1
(Pdb) (Pdb) ['D:\\SynologyDrive\\CodeLearning\\WIN\\pytest-book\\src\\chapter-2', 'C:\\Program Files\\JetBrains\\PyCharm Community Edition 2022.3.2\\plugins\\python-ce\\helpers\\pycharm', 'D:\\SynologyDrive\\CodeLearning\\WIN\\pytest-book', 'C:\\Users\\guoliang\\AppData\\Local\\Programs\\Python\\Python37\\python37.zip', 'C:\\Users\\guoliang\\AppData\\Local\\Programs\\Python\\Python37\\DLLs', 'C:\\Users\\guoliang\\AppData\\Local\\Programs\\Python\\Python37\\lib', 'C:\\Users\\guoliang\\AppData\\Local\\Programs\\Python\\Python37', 'D:\\SynologyDrive\\CodeLearning\\WIN\\pytest-book\\venv', 'D:\\SynologyDrive\\CodeLearning\\WIN\\pytest-book\\venv\\lib\\site-packages']
(Pdb) 
>>>>>>>>>>>>>>>>>>>>> PDB continue (IO-capturing resumed) >>>>>>>>>>>>>>>>>>>>>
FAILED                                            [100%]
test_pdb.py:0 (test_fail)
1 != 0

Expected :0
Actual   :1
<Click to see difference>

def test_fail():
        x = 1
        import pdb
        pdb.set_trace()
>       assert x == 0
E       assert 1 == 0
E         +1
E         -0

test_pdb.py:5: AssertionError


================================== FAILURES ===================================
__________________________________ test_fail __________________________________

    def test_fail():
        x = 1
        import pdb
        pdb.set_trace()
>       assert x == 0
E       assert 1 == 0
E         +1
E         -0

test_pdb.py:5: AssertionError
=========================== short test summary info ===========================
FAILED test_pdb.py::test_fail - assert 1 == 0
============================= 1 failed in 31.71s ==============================

Process finished with exit code 1






           

使用内置的中斷函數

Python 3.7中有一個内置breakpoint()函數。pytest可以在以下場景中使用:

當breakpoint()被調用,并且PYTHONBREAKPOINT為None時,pytest會使用内部自定義的PDB代替系統的PDB,測試執行結束時,自動切換回系統自帶的PDB。

當加上--pdb選項時,breakpoint()和測試發生錯誤時,都會調用内部自定義的PDB,--pdbcls選項允許指定一個使用者自定義的PDB類。

錯誤句柄

這是pytest 5.0版本新增特性,在測試中發生段錯誤或者逾時的情況下,faulthandler标準子產品可以轉存Python的回溯資訊,它在pytest的執行中預設為已加載,使用-p no:faulthandler選項可以關閉它。同樣,faulthandler_timeout=X配置項可用于當測試用例的完成時間超過X秒時,轉存所有線程的Python回溯資訊。

舉例說明,在配置檔案中設定測試執行的逾時時間為5s,代碼如下:

[pytest]
faulthandler_timeout=5           

在測試用例中添加等待7s的操作,代碼如下:

import time

def test_faulthandler():
    time.sleep(7)
    assert 1           

當預設已加載faulthandler時,輸入pytest-q test_fault_handler.py,執行結果如下,顯示Timeout(0:00:05)。在執行剛超過5s的時候會列印出回溯資訊,但不會中斷測試的執行。

執行結果如下:

PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2> pytest -q .\test_fault_handler.py
Timeout (0:00:05)!
Thread 0x00005e58 (most recent call first):
  File "D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2\test_fault_handler.py", line 4 in test_faulthandler
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\python.py", line 184 in pytest_pyfunc_call
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\callers.py", line 187 in _multicall
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\manager.py", line 87 in <lambda>
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\manager.py", line 93 in _hookexec
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\hooks.py", line 286 in __call__
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\python.py", line 1479 in runtest
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\runner.py", line 135 in pytest_runtest_call
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\callers.py", line 187 in _multicall
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\manager.py", line 87 in <lambda>
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\manager.py", line 93 in _hookexec
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\hooks.py", line 286 in __call__
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\runner.py", line 217 in <lambda>
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\runner.py", line 244 in from_call
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\runner.py", line 217 in call_runtest_hook
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\runner.py", line 186 in call_and_report
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\runner.py", line 100 in runtestprotocol
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\runner.py", line 85 in pytest_runtest_protocol
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\callers.py", line 187 in _multicall
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\manager.py", line 87 in <lambda>
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\manager.py", line 93 in _hookexec
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\hooks.py", line 286 in __call__
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\main.py", line 272 in pytest_runtestloop
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\callers.py", line 187 in _multicall
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\manager.py", line 87 in <lambda>
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\manager.py", line 93 in _hookexec
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\hooks.py", line 286 in __call__
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\main.py", line 247 in _main
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\main.py", line 191 in wrap_session
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\main.py", line 240 in pytest_cmdline_main
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\callers.py", line 187 in _multicall
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\manager.py", line 87 in <lambda>
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\manager.py", line 93 in _hookexec
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\pluggy\hooks.py", line 286 in __call__
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\config\__init__.py", line 125 in main
  File "C:\Users\guoliang\AppData\Local\Programs\Python\Python37\Scripts\pytest.exe\__main__.py", line 9 in <module>
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\runpy.py", line 85 in _run_code
  File "c:\users\guoliang\appdata\local\programs\python\python37\lib\runpy.py", line 193 in _run_module_as_main
.                                                                                                                                                                                                                                [100%]
1 passed in 7.09s
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2> 
           

接下來看一下去掉faulthandler的情況,逾時并不會觸發回溯資訊的列印,輸入pytest -q -p no:faulthandler test_fault_handler.py之後的結果如下:

PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2> pytest -q -p no:faulthandler test_fault_handler.py
.                                                                                                                                                                                                                                [100%]
1 passed in 7.02s
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2> 
           

注意:這個功能是從pytest -faulthandler插件合并而來的,但是有兩點不同:去掉此功能時,使用-p no:faulthandler代替原來的--no-faulthandler;使用faulthandler_timeout配置項代替--faulthandler-timeout指令行選項來配置逾時時間。當然,也可以使用-o faulthandler_timeout=X在指令行進行配置。

繼續閱讀