diff options
Diffstat (limited to 'Lib/unittest')
-rw-r--r-- | Lib/unittest/__init__.py | 3 | ||||
-rw-r--r-- | Lib/unittest/case.py | 11 | ||||
-rw-r--r-- | Lib/unittest/result.py | 2 | ||||
-rw-r--r-- | Lib/unittest/suite.py | 199 |
4 files changed, 205 insertions, 10 deletions
diff --git a/Lib/unittest/__init__.py b/Lib/unittest/__init__.py index 4a308fa..06fe55d 100644 --- a/Lib/unittest/__init__.py +++ b/Lib/unittest/__init__.py @@ -51,13 +51,12 @@ __all__ = ['TestResult', 'TestCase', 'TestSuite', # Expose obsolete functions for backwards compatibility __all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases']) -__all__.append('_TextTestResult') from .result import TestResult from .case import (TestCase, FunctionTestCase, SkipTest, skip, skipIf, skipUnless, expectedFailure) -from .suite import TestSuite +from .suite import BaseTestSuite, TestSuite from .loader import (TestLoader, defaultTestLoader, makeSuite, getTestCaseNames, findTestCases) from .main import TestProgram, main diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 19b196c..0bfcc75 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -153,6 +153,9 @@ class TestCase(object): longMessage = False + # Attribute used by TestSuite for classSetUp + + _classSetupFailed = False def __init__(self, methodName='runTest'): """Create an instance of the class that will use the named test @@ -211,6 +214,14 @@ class TestCase(object): "Hook method for deconstructing the test fixture after testing it." pass + @classmethod + def setUpClass(cls): + "Hook method for setting up class fixture before running tests in the class." + + @classmethod + def tearDownClass(cls): + "Hook method for deconstructing the class fixture after running all tests in the class." + def countTestCases(self): return 1 diff --git a/Lib/unittest/result.py b/Lib/unittest/result.py index 22e825a..746967e 100644 --- a/Lib/unittest/result.py +++ b/Lib/unittest/result.py @@ -16,6 +16,8 @@ class TestResult(object): contain tuples of (testcase, exceptioninfo), where exceptioninfo is the formatted traceback of the error that occurred. """ + _previousTestClass = None + _moduleSetUpFailed = False def __init__(self, stream=None, descriptions=None, verbosity=None): self.failures = [] self.errors = [] diff --git a/Lib/unittest/suite.py b/Lib/unittest/suite.py index 60e9b6c..cccc7ef 100644 --- a/Lib/unittest/suite.py +++ b/Lib/unittest/suite.py @@ -1,17 +1,13 @@ """TestSuite""" +import sys + from . import case from . import util -class TestSuite(object): - """A test suite is a composite test consisting of a number of TestCases. - - For use, create an instance of TestSuite, then add test case instances. - When all tests have been added, the suite can be passed to a test - runner, such as TextTestRunner. It will run the individual test cases - in the order in which they were added, aggregating the results. When - subclassing, do not forget to call the base class constructor. +class BaseTestSuite(object): + """A simple test suite that doesn't provide class or module shared fixtures. """ def __init__(self, tests=()): self._tests = [] @@ -70,3 +66,190 @@ class TestSuite(object): """Run the tests without collecting errors in a TestResult""" for test in self: test.debug() + + +class TestSuite(BaseTestSuite): + """A test suite is a composite test consisting of a number of TestCases. + + For use, create an instance of TestSuite, then add test case instances. + When all tests have been added, the suite can be passed to a test + runner, such as TextTestRunner. It will run the individual test cases + in the order in which they were added, aggregating the results. When + subclassing, do not forget to call the base class constructor. + """ + + + def run(self, result): + self._wrapped_run(result) + self._tearDownPreviousClass(None, result) + self._handleModuleTearDown(result) + return result + + ################################ + # private methods + def _wrapped_run(self, result): + for test in self: + if result.shouldStop: + break + + if _isnotsuite(test): + self._tearDownPreviousClass(test, result) + self._handleModuleFixture(test, result) + self._handleClassSetUp(test, result) + result._previousTestClass = test.__class__ + + if (getattr(test.__class__, '_classSetupFailed', False) or + getattr(result, '_moduleSetUpFailed', False)): + continue + + if hasattr(test, '_wrapped_run'): + test._wrapped_run(result) + else: + test(result) + + def _handleClassSetUp(self, test, result): + previousClass = getattr(result, '_previousTestClass', None) + currentClass = test.__class__ + if currentClass == previousClass: + return + if result._moduleSetUpFailed: + return + if getattr(currentClass, "__unittest_skip__", False): + return + + currentClass._classSetupFailed = False + + setUpClass = getattr(currentClass, 'setUpClass', None) + if setUpClass is not None: + try: + setUpClass() + except: + currentClass._classSetupFailed = True + self._addClassSetUpError(result, currentClass) + + def _get_previous_module(self, result): + previousModule = None + previousClass = getattr(result, '_previousTestClass', None) + if previousClass is not None: + previousModule = previousClass.__module__ + return previousModule + + + def _handleModuleFixture(self, test, result): + previousModule = self._get_previous_module(result) + currentModule = test.__class__.__module__ + if currentModule == previousModule: + return + + self._handleModuleTearDown(result) + + + result._moduleSetUpFailed = False + try: + module = sys.modules[currentModule] + except KeyError: + return + setUpModule = getattr(module, 'setUpModule', None) + if setUpModule is not None: + try: + setUpModule() + except: + result._moduleSetUpFailed = True + error = _ErrorHolder('setUpModule (%s)' % currentModule) + result.addError(error, sys.exc_info()) + + def _handleModuleTearDown(self, result): + previousModule = self._get_previous_module(result) + if previousModule is None: + return + if result._moduleSetUpFailed: + return + + try: + module = sys.modules[previousModule] + except KeyError: + return + + tearDownModule = getattr(module, 'tearDownModule', None) + if tearDownModule is not None: + try: + tearDownModule() + except: + error = _ErrorHolder('tearDownModule (%s)' % previousModule) + result.addError(error, sys.exc_info()) + + def _tearDownPreviousClass(self, test, result): + previousClass = getattr(result, '_previousTestClass', None) + currentClass = test.__class__ + if currentClass == previousClass: + return + if getattr(previousClass, '_classSetupFailed', False): + return + if getattr(result, '_moduleSetUpFailed', False): + return + if getattr(previousClass, "__unittest_skip__", False): + return + + tearDownClass = getattr(previousClass, 'tearDownClass', None) + if tearDownClass is not None: + try: + tearDownClass() + except: + self._addClassTearDownError(result) + + def _addClassTearDownError(self, result): + className = util.strclass(result._previousTestClass) + error = _ErrorHolder('classTearDown (%s)' % className) + result.addError(error, sys.exc_info()) + + def _addClassSetUpError(self, result, klass): + className = util.strclass(klass) + error = _ErrorHolder('classSetUp (%s)' % className) + result.addError(error, sys.exc_info()) + + +class _ErrorHolder(object): + """ + Placeholder for a TestCase inside a result. As far as a TestResult + is concerned, this looks exactly like a unit test. Used to insert + arbitrary errors into a test suite run. + """ + # Inspired by the ErrorHolder from Twisted: + # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py + + # attribute used by TestResult._exc_info_to_string + failureException = None + + def __init__(self, description): + self.description = description + + def id(self): + return self.description + + def shortDescription(self): + return None + + def __repr__(self): + return "<ErrorHolder description=%r>" % (self.description,) + + def __str__(self): + return self.id() + + def run(self, result): + # could call result.addError(...) - but this test-like object + # shouldn't be run anyway + pass + + def __call__(self, result): + return self.run(result) + + def countTestCases(self): + return 0 + +def _isnotsuite(test): + "A crude way to tell apart testcases and suites with duck-typing" + try: + iter(test) + except TypeError: + return True + return False |