天天看点

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在命令行进行配置。

继续阅读