Python 单元测试:测试失败时自动运行调试器

发布于 2024-10-06 16:16:02 字数 809 浏览 1 评论 0原文

有没有办法在单元测试失败时自动启动调试器?

现在我只是手动使用 pdb.set_trace() ,但这非常繁琐,因为我需要每次添加它并在最后取出它。

例如:

import unittest

class tests(unittest.TestCase):

    def setUp(self):
        pass

    def test_trigger_pdb(self):
        #this is the way I do it now
        try:
            assert 1==0
        except AssertionError:
            import pdb
            pdb.set_trace()

    def test_no_trigger(self):
        #this is the way I would like to do it:
        a=1
        b=2
        assert a==b
        #magically, pdb would start here
        #so that I could inspect the values of a and b

if __name__=='__main__':
    #In the documentation the unittest.TestCase has a debug() method
    #but I don't understand how to use it
    #A=tests()
    #A.debug(A)

    unittest.main()

Is there a way to automatically start the debugger at the point at which a unittest fails?

Right now I am just using pdb.set_trace() manually, but this is very tedious as I need to add it each time and take it out at the end.

For Example:

import unittest

class tests(unittest.TestCase):

    def setUp(self):
        pass

    def test_trigger_pdb(self):
        #this is the way I do it now
        try:
            assert 1==0
        except AssertionError:
            import pdb
            pdb.set_trace()

    def test_no_trigger(self):
        #this is the way I would like to do it:
        a=1
        b=2
        assert a==b
        #magically, pdb would start here
        #so that I could inspect the values of a and b

if __name__=='__main__':
    #In the documentation the unittest.TestCase has a debug() method
    #but I don't understand how to use it
    #A=tests()
    #A.debug(A)

    unittest.main()

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

紅太極 2024-10-13 16:16:02

我认为您正在寻找的是鼻子。它的工作原理类似于 unittest 的测试运行程序。

您可以使用以下命令进入错误调试器:

nosetests --pdb

I think what you are looking for is nose. It works like a test runner for unittest.

You can drop into the debugger on errors, with the following command:

nosetests --pdb
忆沫 2024-10-13 16:16:02
import unittest
import sys
import pdb
import functools
import traceback
def debug_on(*exceptions):
    if not exceptions:
        exceptions = (AssertionError, )
    def decorator(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except exceptions:
                info = sys.exc_info()
                traceback.print_exception(*info) 
                pdb.post_mortem(info[2])
        return wrapper
    return decorator

class tests(unittest.TestCase):
    @debug_on()
    def test_trigger_pdb(self):
        assert 1 == 0

我更正了代码以在异常上调用 post_mortem 而不是 set_trace。

import unittest
import sys
import pdb
import functools
import traceback
def debug_on(*exceptions):
    if not exceptions:
        exceptions = (AssertionError, )
    def decorator(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except exceptions:
                info = sys.exc_info()
                traceback.print_exception(*info) 
                pdb.post_mortem(info[2])
        return wrapper
    return decorator

class tests(unittest.TestCase):
    @debug_on()
    def test_trigger_pdb(self):
        assert 1 == 0

I corrected the code to call post_mortem on the exception instead of set_trace.

允世 2024-10-13 16:16:02

第三方测试框架增强功能通常似乎包括该功能(其他答案中已经提到了nose 和nose2)。更多:

pytest 支持。

pytest --pdb

或者,如果您使用 absl-pyabsltest 而不是unittest 模块:

name_of_test.py --pdb_post_mortem

Third party test framework enhancements generally seem to include the feature (nose and nose2 were already mentioned in other answers). Some more:

pytest supports it.

pytest --pdb

Or if you use absl-py's absltest instead of unittest module:

name_of_test.py --pdb_post_mortem
我不吻晚风 2024-10-13 16:16:02

一个简单的选择是只运行测试而不收集结果,并让第一个异常崩溃在堆栈中(用于任意事后处理),例如

try: unittest.findTestCases(__main__).debug()
except:
    pdb.post_mortem(sys.exc_info()[2])

另一个选项:覆盖 unittest.TextTestResultaddErroraddFailure 在调试测试运行器中用于立即 post_mortem 调试(在 tearDown() 之前) - 或者用于收集和处理错误&以高级方式回溯。

(不需要额外的框架或测试方法的额外装饰器)

基本示例:

import unittest, pdb

class TC(unittest.TestCase):
    def testZeroDiv(self):
        1 / 0

def debugTestRunner(post_mortem=None):
    """unittest runner doing post mortem debugging on failing tests"""
    if post_mortem is None:
        post_mortem = pdb.post_mortem
    class DebugTestResult(unittest.TextTestResult):
        def addError(self, test, err):
            # called before tearDown()
            traceback.print_exception(*err)
            post_mortem(err[2])
            super(DebugTestResult, self).addError(test, err)
        def addFailure(self, test, err):
            traceback.print_exception(*err)
            post_mortem(err[2])
            super(DebugTestResult, self).addFailure(test, err)
    return unittest.TextTestRunner(resultclass=DebugTestResult)

if __name__ == '__main__':
    ##unittest.main()
    unittest.main(testRunner=debugTestRunner())
    ##unittest.main(testRunner=debugTestRunner(pywin.debugger.post_mortem))
    ##unittest.findTestCases(__main__).debug()

A simple option is to just run the tests without result collection and letting the first exception crash down the stack (for arbitrary post mortem handling) by e.g.

try: unittest.findTestCases(__main__).debug()
except:
    pdb.post_mortem(sys.exc_info()[2])

Another option: Override unittest.TextTestResult's addError and addFailure in a debug test runner for immediate post_mortem debugging (before tearDown()) - or for collecting and handling errors & tracebacks in an advanced way.

(Doesn't require extra frameworks or an extra decorator for test methods)

Basic example:

import unittest, pdb

class TC(unittest.TestCase):
    def testZeroDiv(self):
        1 / 0

def debugTestRunner(post_mortem=None):
    """unittest runner doing post mortem debugging on failing tests"""
    if post_mortem is None:
        post_mortem = pdb.post_mortem
    class DebugTestResult(unittest.TextTestResult):
        def addError(self, test, err):
            # called before tearDown()
            traceback.print_exception(*err)
            post_mortem(err[2])
            super(DebugTestResult, self).addError(test, err)
        def addFailure(self, test, err):
            traceback.print_exception(*err)
            post_mortem(err[2])
            super(DebugTestResult, self).addFailure(test, err)
    return unittest.TextTestRunner(resultclass=DebugTestResult)

if __name__ == '__main__':
    ##unittest.main()
    unittest.main(testRunner=debugTestRunner())
    ##unittest.main(testRunner=debugTestRunner(pywin.debugger.post_mortem))
    ##unittest.findTestCases(__main__).debug()
苦行僧 2024-10-13 16:16:02

@cmcginty的答案应用于继任者nose 2 (由nose推荐,在基于Debian的系统上可通过apt-get install noreferrer获得),您可以

nose2

/en/latest/plugins/debugger.html" rel="nofollow noreferrer">通过调用测试

。为此,您需要在主目录中有一个合适的 .unittest.cfg 或在项目目录中有一个 unittest.cfg ;它需要包含以下行

[debugger]
always-on = True
errors-only = False

To apply @cmcginty's answer to the successor nose 2 (recommended by nose available on Debian-based systems via apt-get install nose2), you can drop into the debugger on failures and errors by calling

nose2

in your test directory.

For this, you need to have a suitable .unittest.cfg in your home directory or unittest.cfg in the project directory; it needs to contain the lines

[debugger]
always-on = True
errors-only = False
无所的.畏惧 2024-10-13 16:16:02

要解决代码中的注释“在文档中,unittest.TestCase 有一个 debug() 方法,但我不明白如何使用它”,您可以执行以下操作:

suite = unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__])
suite.debug()

创建单独的测试用例,如下所示:
testCase = tests('test_trigger_pdb') (其中 testsTestCase 的子类,根据您的示例)。然后您可以执行 testCase.debug() 来调试一个案例。

To address the comment in your code "In the documentation the unittest.TestCase has a debug() method but I don't understand how to use it", you can do something like this:

suite = unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__])
suite.debug()

Individual test cases are created like:
testCase = tests('test_trigger_pdb') (where tests is a sub-class of TestCase as per your example). And then you can do testCase.debug() to debug one case.

川水往事 2024-10-13 16:16:02

这是一个内置的、没有额外模块的解决方案:

import unittest
import sys
import pdb

####################################
def ppdb(e=None):
    """conditional debugging
       use with:  `if ppdb(): pdb.set_trace()` 
    """
    return ppdb.enabled

ppdb.enabled = False
###################################


class SomeTest(unittest.TestCase):

    def test_success(self):
        try:
            pass
        except Exception, e:
            if ppdb(): pdb.set_trace()
            raise

    def test_fail(self):
        try:
            res = 1/0
            #note:  a `nosetests --pdb` run will stop after any exception
            #even one without try/except and ppdb() does not not modify that.
        except Exception, e:
            if ppdb(): pdb.set_trace()
            raise


if __name__ == '__main__':
    #conditional debugging, but not in nosetests
    if "--pdb" in sys.argv:
        print "pdb requested"
        ppdb.enabled = not sys.argv[0].endswith("nosetests")
        sys.argv.remove("--pdb")

    unittest.main()

使用 python myunittest.py --pdb 调用它,它将停止。不然不会。

Here's a built-in, no extra modules, solution:

import unittest
import sys
import pdb

####################################
def ppdb(e=None):
    """conditional debugging
       use with:  `if ppdb(): pdb.set_trace()` 
    """
    return ppdb.enabled

ppdb.enabled = False
###################################


class SomeTest(unittest.TestCase):

    def test_success(self):
        try:
            pass
        except Exception, e:
            if ppdb(): pdb.set_trace()
            raise

    def test_fail(self):
        try:
            res = 1/0
            #note:  a `nosetests --pdb` run will stop after any exception
            #even one without try/except and ppdb() does not not modify that.
        except Exception, e:
            if ppdb(): pdb.set_trace()
            raise


if __name__ == '__main__':
    #conditional debugging, but not in nosetests
    if "--pdb" in sys.argv:
        print "pdb requested"
        ppdb.enabled = not sys.argv[0].endswith("nosetests")
        sys.argv.remove("--pdb")

    unittest.main()

call it with python myunittest.py --pdb and it will halt. Otherwise it won't.

背叛残局 2024-10-13 16:16:02

上面的一些解决方案修改了业务逻辑:

try:      # <-- new code
 original_code()  # <-- changed (indented)
except Exception as e:  # <-- new code
 pdb.post_mortem(...)   # <-- new code

为了最小化对原始代码的更改,我们可以定义一个函数装饰器,并简单地装饰抛出的函数:

def pm(func):
    import functools, pdb

    @functools.wraps(func)
    def func2(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            pdb.post_mortem(e.__traceback__)

   raise
    return func2

使用:

@pm
def test_xxx(...):
 ...

Some solution above modifies business logic:

try:      # <-- new code
 original_code()  # <-- changed (indented)
except Exception as e:  # <-- new code
 pdb.post_mortem(...)   # <-- new code

To minimize changes to the original code, we can define a function decorator, and simply decorate the function that's throwing:

def pm(func):
    import functools, pdb

    @functools.wraps(func)
    def func2(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            pdb.post_mortem(e.__traceback__)

   raise
    return func2

Use:

@pm
def test_xxx(...):
 ...
琉璃繁缕 2024-10-13 16:16:02

使用装饰器构建一个模块,该装饰器可以对除 AssertionError 之外的每种类型的错误进行分析。装饰器可以由日志记录根级别触发

#!/usr/bin/env python3
'''
Decorator for getting post mortem on errors of a unittest TestCase
'''
import sys
import pdb
import functools
import traceback
import logging
import unittest

logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG)

def debug_on(log_level):
    '''
    Function decorator for post mortem debugging unittest functions.

    Args:
      log_level (int): logging levels coesponding to logging stl module

    Usecase:

    class tests(unittest.TestCase):
        @debug_on(logging.root.level)
        def test_trigger_pdb(self):
            assert 1 == 0
    '''

    def decorator(f):

        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except BaseException as err:
                info = sys.exc_info()
                traceback.print_exception(*info)

                if log_level < logging.INFO and type(err) != AssertionError:
                    pdb.post_mortem(info[2])

        return wrapper

    return decorator


class Debug_onTester(unittest.TestCase):

    @debug_on(logging.root.level)
    def test_trigger_pdb(self):
        assert 1 == 0


if __name__ == '__main__':
    unittest.main()

Buildt a module with a decorator which post mortems into every type of error except AssertionError. The decorator can be triggered by the logging root level

#!/usr/bin/env python3
'''
Decorator for getting post mortem on errors of a unittest TestCase
'''
import sys
import pdb
import functools
import traceback
import logging
import unittest

logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG)

def debug_on(log_level):
    '''
    Function decorator for post mortem debugging unittest functions.

    Args:
      log_level (int): logging levels coesponding to logging stl module

    Usecase:

    class tests(unittest.TestCase):
        @debug_on(logging.root.level)
        def test_trigger_pdb(self):
            assert 1 == 0
    '''

    def decorator(f):

        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except BaseException as err:
                info = sys.exc_info()
                traceback.print_exception(*info)

                if log_level < logging.INFO and type(err) != AssertionError:
                    pdb.post_mortem(info[2])

        return wrapper

    return decorator


class Debug_onTester(unittest.TestCase):

    @debug_on(logging.root.level)
    def test_trigger_pdb(self):
        assert 1 == 0


if __name__ == '__main__':
    unittest.main()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文