【问题标题】:Python Unit Testing: Automatically Running the Debugger when a test failsPython 单元测试:测试失败时自动运行调试器
【发布时间】:2011-05-22 21:19:00
【问题描述】:

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

目前我只是手动使用 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()

【问题讨论】:

    标签: python unit-testing pdb python-unittest


    【解决方案1】:

    我认为您正在寻找的是nose。它就像unittest 的测试运行器一样工作。

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

    nosetests --pdb
    

    【讨论】:

    • 如果您要使用self.assertEquals 而不是普通的assert,这样测试就会出现“失败”而不是“错误”,那么要运行的命令将是nosetest --pdb-failures
    • 在 win32 和 python 2.7 上,我用easy_install nose 安装了nose,但后来发现命令是 nosetests 而不是nosetest。我还必须使用--pdb-failures
    • 在 Ubuntu 上,我也发现这个奇妙的命令被命名为 '''nosetests''',强调复数 's' 的末尾。可以安装包 python-nose (sudo apt-get install python-nose) 来获得这个方便的命令。要运行现有测试,请使用 '''nosetests --pdb-failures ./test_set.py''' 其中 test_set.py 是您现有的单元测试。
    • 对于继任者nose2,请参阅下面的答案。
    • 投反对票,因为鼻子没有维护(见鼻子2)。
    【解决方案2】:
    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。

    【讨论】:

    • 我还建议您使用全局标志来打开和关闭此调试。它会使运行测试更加复杂。如果我运行某人的测试套件并弹出一个调试器,我会非常生气。
    • 罗什,谢谢。它工作得很好。关于全球标志的观点很好:)
    • 目前这是一个不错的解决方案,所以 +1。我会在你的代码中添加这个:import pdb;导入系统;在 pdb.post_mortem(...) 之后,加注。这是应该重新抛出异常的罕见情况。如果用户继续失败的测试用例,否则将被视为通过。另一个注意事项;如果能够检测到 pdb 已经在运行并且不会弹出用于单元测试的调试器,但在调试器下启动时会中断失败,我会更开心。
    • @HeathHunnicutt:听起来可能有一种方法可以检测调试器是否已经在运行,请参阅问题How to detect that Python code is being executed through the debugger?
    【解决方案3】:

    第三方测试框架增强功能通常似乎包括该功能(nosenose2 已在其他答案中提及)。更多:

    pytest 支持。

    pytest --pdb
    

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

    name_of_test.py --pdb_post_mortem
    

    【讨论】:

      【解决方案4】:

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

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

      另一个选项:在调试测试运行程序中覆盖 unittest.TextTestResultaddErroraddFailure 以立即进行事后调试(在 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()
      

      【讨论】:

      • 那需要 unittest.findTestCases('main').debug() 吗?
      【解决方案5】:

      要将@cmcginty's answer 应用于后继nose 2recommended by nose 可通过apt-get install nose2 在基于Debian 的系统上使用),您可以通过调用drop into the debugger 来处理失败和错误

      nose2
      

      在您的测试目录中。

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

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

      【讨论】:

        【解决方案6】:

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

        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 调用它,它会停止。否则不会。

        【讨论】:

          猜你喜欢
          • 2017-10-12
          • 1970-01-01
          • 2023-04-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-02-26
          • 2019-11-29
          相关资源
          最近更新 更多