summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPavel Savchenko <asfaltboy@gmail.com>2016-10-26 23:10:17 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-10-18 12:22:51 +0200
commit1711c509faf3111bdf5a3a860b2cd01c0dc5d233 (patch)
tree84354e38d069e4ce0132288f9bbb0305d7934c4a
parentdc8cd2fefd028913e3273927342374f5259cc566 (diff)
Fixed #27391 -- Implemented SimpleTestCase.debug().
debug() should bubbled up exceptions if occurring in test, but behave the same as run() when no exceptions occurred.
-rw-r--r--django/test/testcases.py25
-rw-r--r--docs/releases/3.1.txt4
-rw-r--r--docs/topics/testing/tools.txt5
-rw-r--r--tests/test_utils/test_simpletestcase.py61
4 files changed, 90 insertions, 5 deletions
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 468c0c4fbc..ca5712ee8d 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -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
diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt
index a42d71c855..a8aaa8e4f9 100644
--- a/docs/releases/3.1.txt
+++ b/docs/releases/3.1.txt
@@ -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
~~~~
diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt
index 41652cbee1..13ae83dc80 100644
--- a/docs/topics/testing/tools.txt
+++ b/docs/topics/testing/tools.txt
@@ -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``
-----------------------
diff --git a/tests/test_utils/test_simpletestcase.py b/tests/test_utils/test_simpletestcase.py
index eb1e0017d2..76ec37e80f 100644
--- a/tests/test_utils/test_simpletestcase.py
+++ b/tests/test_utils/test_simpletestcase.py
@@ -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)