Python Unit Testing: Автоматический запуск отладчика при сбое теста
Заголовок: Как автоматически запустить отладчик в точке, где завершается 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 ответ(ов)
Для запуска тестов с возможностью их отладки при возникновении исключений, можно воспользоваться простым вариантом: выполнить тесты без сбора результатов и позволить первому исключению "упасть" по стеку для произвольной постмортем-обработки, например:
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())
Этот код позволит вам сразу же приступить к отладке в случае возникновения ошибки в тестах, минуя обычный процесс завершения тестов.
Чтобы ответить на комментарий в вашем коде "В документации указано, что у unittest.TestCase есть метод debug(), но я не понимаю, как его использовать", вы можете сделать что-то подобное:
suite = unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__])
suite.debug()
Отдельные тестовые случаи создаются так:
testCase = tests('test_trigger_pdb')
(где tests
является подклассом TestCase
, как в вашем примере). Затем вы можете вызвать testCase.debug()
, чтобы отладить один конкретный случай.
Ваш вопрос касается декоратора 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()
, который останавливает выполнение программы в месте вызова. Если у вас есть дополнительные вопросы или нужна помощь с кодом, не стесняйтесь спрашивать!
Как протестировать, что функция Python вызывает исключение?
Запуск unittest с типичной структурой каталогов тестирования
Написание модульных тестов на Python: С чего начать? [закрыто]
Где размещать юнит-тесты на Python? [закрыто]
Работают ли параметризованные тесты pytest с тестами на основе классов unittest?