2
0
Эх сурвалжийг харах

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

debug() should bubbled up exceptions if occurring in test, but behave
the same as run() when no exceptions occurred.
Pavel Savchenko 8 жил өмнө
parent
commit
1711c509fa

+ 24 - 1
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
 

+ 3 - 1
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
 ~~~~

+ 5 - 0
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``
 -----------------------
 

+ 58 - 3
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)