Skip to content

Commit

Permalink
Fixed #27391 -- Implemented SimpleTestCase.debug().
Browse files Browse the repository at this point in the history
debug() should bubbled up exceptions if occurring in test, but behave
the same as run() when no exceptions occurred.
  • Loading branch information
asfaltboy authored and felixxm committed Oct 18, 2019
1 parent dc8cd2f commit 1711c50
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 5 deletions.
25 changes: 24 additions & 1 deletion django/test/testcases.py
Expand Up @@ -9,6 +9,7 @@
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,
Expand Down Expand Up @@ -235,6 +236,21 @@ def __call__(self, result=None):
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
Expand All @@ -245,13 +261,20 @@ def __call__(self, result=None):
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

Expand Down
4 changes: 3 additions & 1 deletion docs/releases/3.1.txt
Expand Up @@ -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
~~~~
Expand Down
5 changes: 5 additions & 0 deletions docs/topics/testing/tools.txt
Expand Up @@ -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``
-----------------------

Expand Down
61 changes: 58 additions & 3 deletions tests/test_utils/test_simpletestcase.py
Expand Up @@ -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()
Expand Down Expand Up @@ -76,6 +81,58 @@ def test_run_skipped_test_no_cleanup(self, _pre_setup, _post_teardown):
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'))
Expand All @@ -85,6 +142,4 @@ def test_debug_skipped_test_no_cleanup(self, _pre_setup, _post_teardown):
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)

0 comments on commit 1711c50

Please sign in to comment.