0

Python Unit Testing: Автоматический запуск отладчика при сбое теста

4

Заголовок: Как автоматически запустить отладчик в точке, где завершается unittest?

Описание проблемы:

Я ищу способ автоматически запускать отладчик в момент, когда тест unittest проваливается. В данный момент я использую pdb.set_trace() вручную, но это утомительно, так как мне приходится добавлять и удалять этот код каждый раз, когда я пишу тесты.

Пример:

import unittest

class tests(unittest.TestCase):

    def setUp(self):
        pass

    def test_trigger_pdb(self):
        # Это то, как я делаю это сейчас
        try:
            assert 1 == 0
        except AssertionError:
            import pdb
            pdb.set_trace()

    def test_no_trigger(self):
        # Это то, как я хотел бы это делать:
        a = 1
        b = 2
        assert a == b
        # Магически, pdb должен запуститься здесь
        # чтобы я мог просмотреть значения a и b

if __name__ == '__main__':
    # В документации упоминается метод debug() у unittest.TestCase
    # Но я не понимаю, как его использовать
    # A = tests()
    # A.debug(A)

    unittest.main()

Как мне настроить автоматический запуск отладчика в момент, когда тест не проходит, без необходимости вручную добавлять вызовы pdb.set_trace() в каждом тесте?

3 ответ(ов)

0

Для запуска тестов с возможностью их отладки при возникновении исключений, можно воспользоваться простым вариантом: выполнить тесты без сбора результатов и позволить первому исключению "упасть" по стеку для произвольной постмортем-обработки, например:

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

Другой вариант: переопределить методы addError и addFailure в классе unittest.TextTestResult для реализации отладчика тестов, который будет выполнять постмортем-отладку при возникновении ошибок (до вызова tearDown()) или для более сложной обработки ошибок и трассировок.

(Не требует дополнительных фреймворков или декораторов для методов тестирования)

Вот базовый пример:

import unittest, pdb
import traceback

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

def debugTestRunner(post_mortem=None):
    """Реджайнер unittest, выполняющий постмортем-отладку для неудачных тестов"""
    if post_mortem is None:
        post_mortem = pdb.post_mortem
    
    class DebugTestResult(unittest.TextTestResult):
        def addError(self, test, err):
            # вызывается перед 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(testRunner=debugTestRunner())

Этот код позволит вам сразу же приступить к отладке в случае возникновения ошибки в тестах, минуя обычный процесс завершения тестов.

0

Чтобы ответить на комментарий в вашем коде "В документации указано, что у unittest.TestCase есть метод debug(), но я не понимаю, как его использовать", вы можете сделать что-то подобное:

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

Отдельные тестовые случаи создаются так: testCase = tests('test_trigger_pdb') (где tests является подклассом TestCase, как в вашем примере). Затем вы можете вызвать testCase.debug(), чтобы отладить один конкретный случай.

0

Ваш вопрос касается декоратора debug_on, который вы реализовали для отладки и выполнения тестов с использованием unittest. Вы упомянули, что исправили код, чтобы вызывать pdb.post_mortem вместо set_trace.

Ваш исправленный код выглядит корректно. Декоратор debug_on позволяет определять, какие исключения следует обрабатывать, и при возникновении исключения он вызывает pdb.post_mortem, что позволяет вам провести отладку в точке возникновения исключения. Это может быть очень полезно для анализа состояния программы.

Вот краткое описание вашего кода:

import unittest
import sys
import pdb
import functools
import traceback

def debug_on(*exceptions):
    if not exceptions:
        exceptions = (AssertionError, )  # По умолчанию обрабатываем 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  # Это приведет к AssertionError и вызовет отладчик

Как вы и отметили, pdb.post_mortem() обеспечивает возможность отладки именно в месте возникновения исключения, что делает его более удобным для поиска ошибок, чем set_trace(), который останавливает выполнение программы в месте вызова. Если у вас есть дополнительные вопросы или нужна помощь с кодом, не стесняйтесь спрашивать!

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь