]> granicus.if.org Git - python/commitdiff
Merged revisions 78130 via svnmerge from
authorMichael Foord <fuzzyman@voidspace.org.uk>
Wed, 10 Feb 2010 15:51:42 +0000 (15:51 +0000)
committerMichael Foord <fuzzyman@voidspace.org.uk>
Wed, 10 Feb 2010 15:51:42 +0000 (15:51 +0000)
svn+ssh://pythondev@svn.python.org/python/trunk

........
  r78130 | michael.foord | 2010-02-10 14:25:12 +0000 (Wed, 10 Feb 2010) | 1 line

  Issue 7893 and Issue 7588
........

Doc/library/unittest.rst
Lib/test/test_unittest.py
Lib/unittest/__init__.py
Lib/unittest/case.py
Lib/unittest/result.py
Lib/unittest/runner.py
Misc/NEWS

index 72d7dec4b22fe0cd604ce153fad770de5a77ad48..effb1169630e45253ea98fa06af1bb8280cdcbfb 100644 (file)
@@ -195,7 +195,7 @@ individual tests are defined with methods whose names start with the letters
 represent tests.
 
 The crux of each test is a call to :meth:`~TestCase.assertEqual` to check for an
-expected result; :meth:`~TestCase.assert_` to verify a condition; or
+expected result; :meth:`~TestCase.assertTrue` to verify a condition; or
 :meth:`~TestCase.assertRaises` to verify that an expected exception gets raised.
 These methods are used instead of the :keyword:`assert` statement so the test
 runner can accumulate all test results and produce a report.
@@ -677,6 +677,7 @@ Test cases
 
       .. deprecated:: 3.1
          :meth:`failUnless`.
+         :meth:`assert_`; use :meth:`assertTrue`.
 
 
    .. method:: assertEqual(first, second, msg=None)
@@ -1067,14 +1068,13 @@ Test cases
       Returns a description of the test, or :const:`None` if no description
       has been provided.  The default implementation of this method
       returns the first line of the test method's docstring, if available,
-      along with the method name.
-
-      .. versionchanged:: 3.1
-         In earlier versions this only returned the first line of the test
-         method's docstring, if available or the :const:`None`.  That led to
-         undesirable behavior of not printing the test name when someone was
-         thoughtful enough to write a docstring.
+      or :const:`None`.
 
+      .. versionchanged:: 3.1,3.2
+         In 3.1 this was changed to add the test name to the short description
+         even in the presence of a docstring. This caused compatibility issues
+         with unittest extensions and adding the test name was moved to the
+         :class:`TextTestResult`.
 
    .. method:: addTypeEqualityFunc(typeobj, function)
 
@@ -1517,6 +1517,14 @@ Loading and running tests
       The default implementation appends the test to the instance's
       :attr:`unexpectedSuccesses` attribute.
 
+.. class:: TextTestResult(stream, descriptions, verbosity)
+
+    A concrete implementation of :class:`TestResult` used by the
+    :class:`TextTestRunner`.
+
+    .. versionadded:: 3.2
+        This class was previously named ``_TextTestResult``. The old name still
+        exists as an alias but is deprecated.
 
 .. data:: defaultTestLoader
 
@@ -1525,7 +1533,7 @@ Loading and running tests
    instead of repeatedly creating new instances.
 
 
-.. class:: TextTestRunner(stream=sys.stderr, descriptions=True, verbosity=1)
+.. class:: TextTestRunner(stream=sys.stderr, descriptions=True, verbosity=1, runnerclass=None)
 
    A basic test runner implementation which prints results on standard error.  It
    has a few configurable parameters, but is essentially very simple.  Graphical
@@ -1537,6 +1545,12 @@ Loading and running tests
       It is not intended to be called directly, but can be overridden in
       subclasses to provide a custom ``TestResult``.
 
+      ``_makeResult()`` instantiates the class or callable passed in the
+      ``TextTestRunner`` constructor as the ``resultclass`` argument. It
+      defaults to :class::`TextTestResult` if no ``resultclass`` is provided.
+      The result class is instantiated with the following arguments::
+
+        stream, descriptions, verbosity
 
 .. function:: main(module='__main__', defaultTest=None, argv=None, testRunner=None, testLoader=unittest.loader.defaultTestLoader, exit=True, verbosity=1)
 
index e5219fdbcbe341cba18123a2e4487b781a974079..0727931fdea3dceef29d01f29e2d50cc664c007f 100644 (file)
@@ -2044,6 +2044,35 @@ class Test_TestResult(TestCase):
         self.assertTrue(test_case is test)
         self.assertIsInstance(formatted_exc, str)
 
+    def testGetDescriptionWithoutDocstring(self):
+        result = unittest.TextTestResult(None, True, None)
+        self.assertEqual(
+                result.getDescription(self),
+                'testGetDescriptionWithoutDocstring (' + __name__ +
+                '.Test_TestResult)')
+
+    def testGetDescriptionWithOneLineDocstring(self):
+        """Tests getDescription() for a method with a docstring."""
+        result = unittest.TextTestResult(None, True, None)
+        self.assertEqual(
+                result.getDescription(self),
+               ('testGetDescriptionWithOneLineDocstring '
+                '(' + __name__ + '.Test_TestResult)\n'
+                'Tests getDescription() for a method with a docstring.'))
+
+    def testGetDescriptionWithMultiLineDocstring(self):
+        """Tests getDescription() for a method with a longer docstring.
+        The second line of the docstring.
+        """
+        result = unittest.TextTestResult(None, True, None)
+        self.assertEqual(
+                result.getDescription(self),
+               ('testGetDescriptionWithMultiLineDocstring '
+                '(' + __name__ + '.Test_TestResult)\n'
+                'Tests getDescription() for a method with a longer '
+                'docstring.'))
+
+
 ### Support code for Test_TestCase
 ################################################################
 
@@ -2458,18 +2487,13 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
         self.assertEqual(events, expected)
 
     def testShortDescriptionWithoutDocstring(self):
-        self.assertEqual(
-                self.shortDescription(),
-                'testShortDescriptionWithoutDocstring (' + __name__ +
-                '.Test_TestCase)')
+        self.assertIsNone(self.shortDescription())
 
     def testShortDescriptionWithOneLineDocstring(self):
         """Tests shortDescription() for a method with a docstring."""
         self.assertEqual(
                 self.shortDescription(),
-                ('testShortDescriptionWithOneLineDocstring '
-                 '(' + __name__ + '.Test_TestCase)\n'
-                 'Tests shortDescription() for a method with a docstring.'))
+                'Tests shortDescription() for a method with a docstring.')
 
     def testShortDescriptionWithMultiLineDocstring(self):
         """Tests shortDescription() for a method with a longer docstring.
@@ -2480,10 +2504,8 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
         """
         self.assertEqual(
                 self.shortDescription(),
-                ('testShortDescriptionWithMultiLineDocstring '
-                 '(' + __name__ + '.Test_TestCase)\n'
                  'Tests shortDescription() for a method with a longer '
-                 'docstring.'))
+                 'docstring.')
 
     def testAddTypeEqualityFunc(self):
         class SadSnake(object):
@@ -3472,6 +3494,19 @@ class Test_TextTestRunner(TestCase):
             # StringIO objects never compare equal, a cheap test instead.
             self.assertEqual(obj.stream.getvalue(), stream.getvalue())
 
+    def test_resultclass(self):
+        def MockResultClass(*args):
+            return args
+        STREAM = object()
+        DESCRIPTIONS = object()
+        VERBOSITY = object()
+        runner = unittest.TextTestRunner(STREAM, DESCRIPTIONS, VERBOSITY,
+                                         resultclass=MockResultClass)
+        self.assertEqual(runner.resultclass, MockResultClass)
+
+        expectedresult = (runner.stream, DESCRIPTIONS, VERBOSITY)
+        self.assertEqual(runner._makeResult(), expectedresult)
+
 
 class TestDiscovery(TestCase):
 
index e0c64d4d48e3d02060f3f69eac76a87928d14035..c12e669790f5db0e7786a53739c4bf9117e6f0e2 100644 (file)
@@ -60,4 +60,7 @@ from .suite import TestSuite
 from .loader import (TestLoader, defaultTestLoader, makeSuite, getTestCaseNames,
                      findTestCases)
 from .main import TestProgram, main
-from .runner import TextTestRunner
+from .runner import TextTestRunner, TextTestResult
+
+# deprecated
+_TextTestResult = TextTestResult
index cf180d7554693e226b0c82a6640a6d1d0f005e31..cd0764d5c24efb4a20538caf1c88352ca002870a 100644 (file)
@@ -229,18 +229,15 @@ class TestCase(object):
         return result.TestResult()
 
     def shortDescription(self):
-        """Returns both the test method name and first line of its docstring.
+        """Returns a one-line description of the test, or None if no
+        description has been provided.
 
-        If no docstring is given, only returns the method name.
+        The default implementation of this method returns the first line of
+        the specified test method's docstring.
         """
-        desc = str(self)
-        doc_first_line = None
+        doc = self._testMethodDoc
+        return doc and doc.split("\n")[0].strip() or None
 
-        if self._testMethodDoc:
-            doc_first_line = self._testMethodDoc.split("\n")[0].strip()
-        if doc_first_line:
-            desc = '\n'.join((desc, doc_first_line))
-        return desc
 
     def id(self):
         return "%s.%s" % (util.strclass(self.__class__), self._testMethodName)
@@ -501,7 +498,6 @@ class TestCase(object):
     assertNotEquals = assertNotEqual
     assertAlmostEquals = assertAlmostEqual
     assertNotAlmostEquals = assertNotAlmostEqual
-    assert_ = assertTrue
 
     # These fail* assertion method names are pending deprecation and will
     # be a DeprecationWarning in 3.2; http://bugs.python.org/issue2578
@@ -518,6 +514,7 @@ class TestCase(object):
     failUnlessAlmostEqual = _deprecate(assertAlmostEqual)
     failIfAlmostEqual = _deprecate(assertNotAlmostEqual)
     failUnless = _deprecate(assertTrue)
+    assert_ = _deprecate(assertTrue)
     failUnlessRaises = _deprecate(assertRaises)
     failIf = _deprecate(assertFalse)
 
index 3e32eb0163f7795d46ccbd1f2f4ff710f1a60c6b..4538304908a51d40bb3eeb39b278d68310c989ad 100644 (file)
@@ -27,7 +27,7 @@ class TestResult(object):
 
     def startTest(self, test):
         "Called when the given test is about to be run"
-        self.testsRun = self.testsRun + 1
+        self.testsRun += 1
 
     def startTestRun(self):
         """Called once before any tests are executed.
@@ -36,8 +36,7 @@ class TestResult(object):
         """
 
     def stopTest(self, test):
-        "Called when the given test has been run"
-        pass
+        """Called when the given test has been run"""
 
     def stopTestRun(self):
         """Called once after all tests are executed.
index 3ed9447164034d6946d88b87a081a9f77779c623..3f45f73638dfeaa5ff778bba2aeeabcbb2e81fea 100644 (file)
@@ -22,7 +22,7 @@ class _WritelnDecorator(object):
         self.write('\n') # text-mode streams translate to \r\n if needed
 
 
-class _TextTestResult(result.TestResult):
+class TextTestResult(result.TestResult):
     """A test result class that can print formatted text results to a stream.
 
     Used by TextTestRunner.
@@ -31,27 +31,28 @@ class _TextTestResult(result.TestResult):
     separator2 = '-' * 70
 
     def __init__(self, stream, descriptions, verbosity):
-        super(_TextTestResult, self).__init__()
+        super(TextTestResult, self).__init__()
         self.stream = stream
         self.showAll = verbosity > 1
         self.dots = verbosity == 1
         self.descriptions = descriptions
 
     def getDescription(self, test):
-        if self.descriptions:
-            return test.shortDescription() or str(test)
+        doc_first_line = test.shortDescription()
+        if self.descriptions and doc_first_line:
+            return '\n'.join((str(test), doc_first_line))
         else:
             return str(test)
 
     def startTest(self, test):
-        super(_TextTestResult, self).startTest(test)
+        super(TextTestResult, self).startTest(test)
         if self.showAll:
             self.stream.write(self.getDescription(test))
             self.stream.write(" ... ")
             self.stream.flush()
 
     def addSuccess(self, test):
-        super(_TextTestResult, self).addSuccess(test)
+        super(TextTestResult, self).addSuccess(test)
         if self.showAll:
             self.stream.writeln("ok")
         elif self.dots:
@@ -59,7 +60,7 @@ class _TextTestResult(result.TestResult):
             self.stream.flush()
 
     def addError(self, test, err):
-        super(_TextTestResult, self).addError(test, err)
+        super(TextTestResult, self).addError(test, err)
         if self.showAll:
             self.stream.writeln("ERROR")
         elif self.dots:
@@ -67,7 +68,7 @@ class _TextTestResult(result.TestResult):
             self.stream.flush()
 
     def addFailure(self, test, err):
-        super(_TextTestResult, self).addFailure(test, err)
+        super(TextTestResult, self).addFailure(test, err)
         if self.showAll:
             self.stream.writeln("FAIL")
         elif self.dots:
@@ -75,7 +76,7 @@ class _TextTestResult(result.TestResult):
             self.stream.flush()
 
     def addSkip(self, test, reason):
-        super(_TextTestResult, self).addSkip(test, reason)
+        super(TextTestResult, self).addSkip(test, reason)
         if self.showAll:
             self.stream.writeln("skipped {0!r}".format(reason))
         elif self.dots:
@@ -83,7 +84,7 @@ class _TextTestResult(result.TestResult):
             self.stream.flush()
 
     def addExpectedFailure(self, test, err):
-        super(_TextTestResult, self).addExpectedFailure(test, err)
+        super(TextTestResult, self).addExpectedFailure(test, err)
         if self.showAll:
             self.stream.writeln("expected failure")
         elif self.dots:
@@ -91,7 +92,7 @@ class _TextTestResult(result.TestResult):
             self.stream.flush()
 
     def addUnexpectedSuccess(self, test):
-        super(_TextTestResult, self).addUnexpectedSuccess(test)
+        super(TextTestResult, self).addUnexpectedSuccess(test)
         if self.showAll:
             self.stream.writeln("unexpected success")
         elif self.dots:
@@ -118,13 +119,18 @@ class TextTestRunner(object):
     It prints out the names of tests as they are run, errors as they
     occur, and a summary of the results at the end of the test run.
     """
-    def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1):
+    resultclass = TextTestResult
+
+    def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1,
+                 resultclass=None):
         self.stream = _WritelnDecorator(stream)
         self.descriptions = descriptions
         self.verbosity = verbosity
+        if resultclass is not None:
+            self.resultclass = resultclass
 
     def _makeResult(self):
-        return _TextTestResult(self.stream, self.descriptions, self.verbosity)
+        return self.resultclass(self.stream, self.descriptions, self.verbosity)
 
     def run(self, test):
         "Run the given test case or test suite."
index e544868c226ed995cb9a6138cef38758ecdb9be5..cb489dd4f26dec71ba0881ac6d8ba94a16a4acad 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -447,6 +447,13 @@ Library
   unpickled. This fixes crashes under Windows when trying to run
   test_multiprocessing in verbose mode.
 
+- Issue #7893: ``unittest.TextTestResult`` is made public and a ``resultclass``
+  argument added to the TextTestRunner constructor allowing a different result
+  class to be used without having to subclass.
+  
+- Issue 7588: ``unittest.TextTestResult.getDescription`` now includes the test
+  name in failure reports even if the test has a docstring.
+
 - Issue #3001: Add a C implementation of recursive locks which is used by
   default when instantiating a `threading.RLock` object. This makes 
   recursive locks as fast as regular non-recursive locks (previously,