This commit is contained in:
Michael Foord 2009-05-02 22:43:34 +00:00
parent 7430989cda
commit 07ef487a96
5 changed files with 257 additions and 39 deletions

View File

@ -1318,6 +1318,20 @@ Loading and running tests
The default implementation does nothing.
.. method:: startTestRun(test)
Called once before any tests are executed.
.. versionadded:: 2.7
.. method:: stopTestRun(test)
Called once before any tests are executed.
.. versionadded:: 2.7
.. method:: addError(test, err)
Called when the test case *test* raises an unexpected exception *err* is a

View File

@ -517,6 +517,10 @@ changes, or look through the Subversion logs for all the details.
If False ``main`` doesn't call :func:`sys.exit` allowing it to
be used from the interactive interpreter. :issue:`3379`.
:class:`TestResult` has new :meth:`startTestRun` and
:meth:`stopTestRun` methods; called immediately before
and after a test run. :issue:`5728` by Robert Collins.
* The :func:`is_zipfile` function in the :mod:`zipfile` module will now
accept a file object, in addition to the path names accepted in earlier
versions. (Contributed by Gabriel Genellina; :issue:`4756`.)

View File

@ -6,6 +6,7 @@ Still need testing:
TestCase.{assert,fail}* methods (some are tested implicitly)
"""
from StringIO import StringIO
import re
from test import test_support
import unittest
@ -26,10 +27,18 @@ class LoggingResult(unittest.TestResult):
self._events.append('startTest')
super(LoggingResult, self).startTest(test)
def startTestRun(self):
self._events.append('startTestRun')
super(LoggingResult, self).startTestRun()
def stopTest(self, test):
self._events.append('stopTest')
super(LoggingResult, self).stopTest(test)
def stopTestRun(self):
self._events.append('stopTestRun')
super(LoggingResult, self).stopTestRun()
def addFailure(self, *args):
self._events.append('addFailure')
super(LoggingResult, self).addFailure(*args)
@ -1817,6 +1826,12 @@ class Test_TestResult(TestCase):
self.assertEqual(result.testsRun, 1)
self.assertEqual(result.shouldStop, False)
# "Called before and after tests are run. The default implementation does nothing."
def test_startTestRun_stopTestRun(self):
result = unittest.TestResult()
result.startTestRun()
result.stopTestRun()
# "addSuccess(test)"
# ...
# "Called when the test case test succeeds"
@ -1964,6 +1979,53 @@ class Foo(unittest.TestCase):
class Bar(Foo):
def test2(self): pass
class LoggingTestCase(unittest.TestCase):
"""A test case which logs its calls."""
def __init__(self, events):
super(LoggingTestCase, self).__init__('test')
self.events = events
def setUp(self):
self.events.append('setUp')
def test(self):
self.events.append('test')
def tearDown(self):
self.events.append('tearDown')
class ResultWithNoStartTestRunStopTestRun(object):
"""An object honouring TestResult before startTestRun/stopTestRun."""
def __init__(self):
self.failures = []
self.errors = []
self.testsRun = 0
self.skipped = []
self.expectedFailures = []
self.unexpectedSuccesses = []
self.shouldStop = False
def startTest(self, test):
pass
def stopTest(self, test):
pass
def addError(self, test):
pass
def addFailure(self, test):
pass
def addSuccess(self, test):
pass
def wasSuccessful(self):
return True
################################################################
### /Support code for Test_TestCase
@ -2058,21 +2120,32 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
events = []
result = LoggingResult(events)
class Foo(unittest.TestCase):
class Foo(LoggingTestCase):
def setUp(self):
events.append('setUp')
super(Foo, self).setUp()
raise RuntimeError('raised by Foo.setUp')
def test(self):
events.append('test')
def tearDown(self):
events.append('tearDown')
Foo('test').run(result)
Foo(events).run(result)
expected = ['startTest', 'setUp', 'addError', 'stopTest']
self.assertEqual(events, expected)
# "With a temporary result stopTestRun is called when setUp errors.
def test_run_call_order__error_in_setUp_default_result(self):
events = []
class Foo(LoggingTestCase):
def defaultTestResult(self):
return LoggingResult(self.events)
def setUp(self):
super(Foo, self).setUp()
raise RuntimeError('raised by Foo.setUp')
Foo(events).run()
expected = ['startTestRun', 'startTest', 'setUp', 'addError',
'stopTest', 'stopTestRun']
self.assertEqual(events, expected)
# "When a setUp() method is defined, the test runner will run that method
# prior to each test. Likewise, if a tearDown() method is defined, the
# test runner will invoke that method after each test. In the example,
@ -2084,20 +2157,32 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
events = []
result = LoggingResult(events)
class Foo(unittest.TestCase):
def setUp(self):
events.append('setUp')
class Foo(LoggingTestCase):
def test(self):
events.append('test')
super(Foo, self).test()
raise RuntimeError('raised by Foo.test')
def tearDown(self):
events.append('tearDown')
expected = ['startTest', 'setUp', 'test', 'addError', 'tearDown',
'stopTest']
Foo('test').run(result)
Foo(events).run(result)
self.assertEqual(events, expected)
# "With a default result, an error in the test still results in stopTestRun
# being called."
def test_run_call_order__error_in_test_default_result(self):
events = []
class Foo(LoggingTestCase):
def defaultTestResult(self):
return LoggingResult(self.events)
def test(self):
super(Foo, self).test()
raise RuntimeError('raised by Foo.test')
expected = ['startTestRun', 'startTest', 'setUp', 'test', 'addError',
'tearDown', 'stopTest', 'stopTestRun']
Foo(events).run()
self.assertEqual(events, expected)
# "When a setUp() method is defined, the test runner will run that method
@ -2111,20 +2196,30 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
events = []
result = LoggingResult(events)
class Foo(unittest.TestCase):
def setUp(self):
events.append('setUp')
class Foo(LoggingTestCase):
def test(self):
events.append('test')
super(Foo, self).test()
self.fail('raised by Foo.test')
def tearDown(self):
events.append('tearDown')
expected = ['startTest', 'setUp', 'test', 'addFailure', 'tearDown',
'stopTest']
Foo('test').run(result)
Foo(events).run(result)
self.assertEqual(events, expected)
# "When a test fails with a default result stopTestRun is still called."
def test_run_call_order__failure_in_test_default_result(self):
class Foo(LoggingTestCase):
def defaultTestResult(self):
return LoggingResult(self.events)
def test(self):
super(Foo, self).test()
self.fail('raised by Foo.test')
expected = ['startTestRun', 'startTest', 'setUp', 'test', 'addFailure',
'tearDown', 'stopTest', 'stopTestRun']
events = []
Foo(events).run()
self.assertEqual(events, expected)
# "When a setUp() method is defined, the test runner will run that method
@ -2138,22 +2233,44 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
events = []
result = LoggingResult(events)
class Foo(unittest.TestCase):
def setUp(self):
events.append('setUp')
def test(self):
events.append('test')
class Foo(LoggingTestCase):
def tearDown(self):
events.append('tearDown')
super(Foo, self).tearDown()
raise RuntimeError('raised by Foo.tearDown')
Foo('test').run(result)
Foo(events).run(result)
expected = ['startTest', 'setUp', 'test', 'tearDown', 'addError',
'stopTest']
self.assertEqual(events, expected)
# "When tearDown errors with a default result stopTestRun is still called."
def test_run_call_order__error_in_tearDown_default_result(self):
class Foo(LoggingTestCase):
def defaultTestResult(self):
return LoggingResult(self.events)
def tearDown(self):
super(Foo, self).tearDown()
raise RuntimeError('raised by Foo.tearDown')
events = []
Foo(events).run()
expected = ['startTestRun', 'startTest', 'setUp', 'test', 'tearDown',
'addError', 'stopTest', 'stopTestRun']
self.assertEqual(events, expected)
# "TestCase.run() still works when the defaultTestResult is a TestResult
# that does not support startTestRun and stopTestRun.
def test_run_call_order_default_result(self):
class Foo(unittest.TestCase):
def defaultTestResult(self):
return ResultWithNoStartTestRunStopTestRun()
def test(self):
pass
Foo('test').run()
# "This class attribute gives the exception raised by the test() method.
# If a test framework needs to use a specialized exception, possibly to
# carry additional information, it must subclass this exception in
@ -2244,7 +2361,9 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
self.failUnless(isinstance(Foo().id(), basestring))
# "If result is omitted or None, a temporary result object is created
# and used, but is not made available to the caller"
# and used, but is not made available to the caller. As TestCase owns the
# temporary result startTestRun and stopTestRun are called.
def test_run__uses_defaultTestResult(self):
events = []
@ -2258,7 +2377,8 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
# Make run() find a result object on its own
Foo('test').run()
expected = ['startTest', 'test', 'addSuccess', 'stopTest']
expected = ['startTestRun', 'startTest', 'test', 'addSuccess',
'stopTest', 'stopTestRun']
self.assertEqual(events, expected)
def testShortDescriptionWithoutDocstring(self):
@ -3215,6 +3335,46 @@ class Test_TestProgram(TestCase):
testLoader=self.FooBarLoader())
class Test_TextTestRunner(TestCase):
"""Tests for TextTestRunner."""
def test_works_with_result_without_startTestRun_stopTestRun(self):
class OldTextResult(ResultWithNoStartTestRunStopTestRun):
separator2 = ''
def printErrors(self):
pass
class Runner(unittest.TextTestRunner):
def __init__(self):
super(Runner, self).__init__(StringIO())
def _makeResult(self):
return OldTextResult()
runner = Runner()
runner.run(unittest.TestSuite())
def test_startTestRun_stopTestRun_called(self):
class LoggingTextResult(LoggingResult):
separator2 = ''
def printErrors(self):
pass
class LoggingRunner(unittest.TextTestRunner):
def __init__(self, events):
super(LoggingRunner, self).__init__(StringIO())
self._events = events
def _makeResult(self):
return LoggingTextResult(self._events)
events = []
runner = LoggingRunner(events)
runner.run(unittest.TestSuite())
expected = ['startTestRun', 'stopTestRun']
self.assertEqual(events, expected)
######################################################################
## Main
######################################################################

View File

@ -186,10 +186,22 @@ class TestResult(object):
"Called when the given test is about to be run"
self.testsRun = self.testsRun + 1
def startTestRun(self):
"""Called once before any tests are executed.
See startTest for a method called before each test.
"""
def stopTest(self, test):
"Called when the given test has been run"
pass
def stopTestRun(self):
"""Called once after all tests are executed.
See stopTest for a method called after each test.
"""
def addError(self, test, err):
"""Called when an error has occurred. 'err' is a tuple of values as
returned by sys.exc_info().
@ -437,8 +449,13 @@ class TestCase(object):
(_strclass(self.__class__), self._testMethodName)
def run(self, result=None):
orig_result = result
if result is None:
result = self.defaultTestResult()
startTestRun = getattr(result, 'startTestRun', None)
if startTestRun is not None:
startTestRun()
self._result = result
result.startTest(self)
testMethod = getattr(self, self._testMethodName)
@ -478,6 +495,10 @@ class TestCase(object):
result.addSuccess(self)
finally:
result.stopTest(self)
if orig_result is None:
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun()
def doCleanups(self):
"""Execute all cleanup functions. Normally called for you after
@ -1433,7 +1454,15 @@ class TextTestRunner(object):
"Run the given test case or test suite."
result = self._makeResult()
startTime = time.time()
test(result)
startTestRun = getattr(result, 'startTestRun', None)
if startTestRun is not None:
startTestRun()
try:
test(result)
finally:
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun()
stopTime = time.time()
timeTaken = stopTime - startTime
result.printErrors()

View File

@ -359,6 +359,17 @@ Library
- unittest.assertNotEqual() now uses the inequality operator (!=) instead
of the equality operator.
- Issue #5679: The methods unittest.TestCase.addCleanup and doCleanups were added.
addCleanup allows you to add cleanup functions that will be called
unconditionally (after setUp if setUp fails, otherwise after tearDown). This
allows for much simpler resource allocation and deallocation during tests.
- Issue #3379: unittest.main now takes an optional exit argument. If False main
doesn't call sys.exit allowing it to be used from the interactive interpreter.
- Issue #5728: unittest.TestResult has new startTestRun and stopTestRun methods;
called immediately before and after a test run.
- Issue #5663: better failure messages for unittest asserts. Default assertTrue
and assertFalse messages are now useful. TestCase has a longMessage attribute.
This defaults to False, but if set to True useful error messages are shown in