Fixed #27391 -- Implemented SimpleTestCase.debug().

debug() should bubbled up exceptions if occurring in test, but behave
the same as run() when no exceptions occurred.
This commit is contained in:
Pavel Savchenko 2016-10-26 23:10:17 +02:00 committed by Mariusz Felisiak
parent dc8cd2fefd
commit 1711c509fa
4 changed files with 90 additions and 5 deletions

View File

@ -9,6 +9,7 @@ from contextlib import contextmanager
from copy import copy
from difflib import get_close_matches
from functools import wraps
from unittest.suite import _DebugResult
from unittest.util import safe_repr
from urllib.parse import (
parse_qsl, unquote, urlencode, urljoin, urlparse, urlsplit, urlunparse,
@ -235,6 +236,21 @@ class SimpleTestCase(unittest.TestCase):
set up. This means that user-defined Test Cases aren't required to
include a call to super().setUp().
"""
self._setup_and_call(result)
def debug(self):
"""Perform the same as __call__(), without catching the exception."""
debug_result = _DebugResult()
self._setup_and_call(debug_result, debug=True)
def _setup_and_call(self, result, debug=False):
"""
Perform the following in order: pre-setup, run test, post-teardown,
skipping pre/post hooks if test is set to be skipped.
If debug=True, reraise any errors in setup and use super().debug()
instead of __call__() to run the test.
"""
testMethod = getattr(self, self._testMethodName)
skipped = (
getattr(self.__class__, "__unittest_skip__", False) or
@ -245,13 +261,20 @@ class SimpleTestCase(unittest.TestCase):
try:
self._pre_setup()
except Exception:
if debug:
raise
result.addError(self, sys.exc_info())
return
super().__call__(result)
if debug:
super().debug()
else:
super().__call__(result)
if not skipped:
try:
self._post_teardown()
except Exception:
if debug:
raise
result.addError(self, sys.exc_info())
return

View File

@ -198,7 +198,9 @@ Templates
Tests
~~~~~
* ...
* :class:`~django.test.SimpleTestCase` now implements the ``debug()`` method to
allow running a test without collecting the result and catching exceptions.
This can be used to support running tests under a debugger.
URLs
~~~~

View File

@ -775,6 +775,11 @@ If your tests make any database queries, use subclasses
:exc:`unittest.SkipTest` in ``setUpClass()``, be sure to do it before
calling ``super()`` to avoid this.
.. versionchanged:: 3.1
The ``debug()`` method was implemented to allow running a test without
collecting the result and catching exceptions.
``TransactionTestCase``
-----------------------

View File

@ -25,6 +25,11 @@ class DebugInvocationTests(SimpleTestCase):
def get_runner(self):
return unittest.TextTestRunner(stream=StringIO())
def isolate_debug_test(self, test_suite, result):
# Suite teardown needs to be manually called to isolate failures.
test_suite._tearDownPreviousClass(None, result)
test_suite._handleModuleTearDown(result)
def test_run_cleanup(self, _pre_setup, _post_teardown):
"""Simple test run: catches errors and runs cleanup."""
test_suite = unittest.TestSuite()
@ -76,6 +81,58 @@ class DebugInvocationTests(SimpleTestCase):
self.assertFalse(_post_teardown.called)
self.assertFalse(_pre_setup.called)
def test_debug_cleanup(self, _pre_setup, _post_teardown):
"""Simple debug run without errors."""
test_suite = unittest.TestSuite()
test_suite.addTest(ErrorTestCase('simple_test'))
test_suite.debug()
_pre_setup.assert_called_once_with()
_post_teardown.assert_called_once_with()
def test_debug_bubbles_error(self, _pre_setup, _post_teardown):
"""debug() bubbles up exceptions before cleanup."""
test_suite = unittest.TestSuite()
test_suite.addTest(ErrorTestCase('raising_test'))
msg = 'debug() bubbles up exceptions before cleanup.'
with self.assertRaisesMessage(Exception, msg):
# This is the same as test_suite.debug().
result = _DebugResult()
test_suite.run(result, debug=True)
# pre-setup is called but not post-teardown.
_pre_setup.assert_called_once_with()
self.assertFalse(_post_teardown.called)
self.isolate_debug_test(test_suite, result)
def test_debug_bubbles_pre_setup_error(self, _pre_setup, _post_teardown):
"""debug() bubbles up exceptions during _pre_setup."""
msg = 'Exception in _pre_setup.'
_pre_setup.side_effect = Exception(msg)
test_suite = unittest.TestSuite()
test_suite.addTest(ErrorTestCase('simple_test'))
with self.assertRaisesMessage(Exception, msg):
# This is the same as test_suite.debug().
result = _DebugResult()
test_suite.run(result, debug=True)
# pre-setup is called but not post-teardown.
_pre_setup.assert_called_once_with()
self.assertFalse(_post_teardown.called)
self.isolate_debug_test(test_suite, result)
def test_debug_bubbles_post_teardown_error(self, _pre_setup, _post_teardown):
"""debug() bubbles up exceptions during _post_teardown."""
msg = 'Exception in _post_teardown.'
_post_teardown.side_effect = Exception(msg)
test_suite = unittest.TestSuite()
test_suite.addTest(ErrorTestCase('simple_test'))
with self.assertRaisesMessage(Exception, msg):
# This is the same as test_suite.debug().
result = _DebugResult()
test_suite.run(result, debug=True)
# pre-setup and post-teardwn are called.
_pre_setup.assert_called_once_with()
_post_teardown.assert_called_once_with()
self.isolate_debug_test(test_suite, result)
def test_debug_skipped_test_no_cleanup(self, _pre_setup, _post_teardown):
test_suite = unittest.TestSuite()
test_suite.addTest(ErrorTestCase('skipped_test'))
@ -85,6 +142,4 @@ class DebugInvocationTests(SimpleTestCase):
test_suite.run(result, debug=True)
self.assertFalse(_post_teardown.called)
self.assertFalse(_pre_setup.called)
# Suite teardown needs to be manually called to isolate failure.
test_suite._tearDownPreviousClass(None, result)
test_suite._handleModuleTearDown(result)
self.isolate_debug_test(test_suite, result)