2 """ This is a hacked version of PyUnit that extends its reporting capabilities
3 with optional meta data on the test cases. It also makes it possible to
4 separate the standard and error output streams in TextTestRunner.
6 It's a hack rather than a set of subclasses because a) Steve had used double
7 underscore private attributes for some things I needed access to, and b) the
8 changes affected so many classes that it was easier just to hack it.
10 The changes are in the following places:
12 - minor refactoring of __init__ and __call__ internals
13 - added some attributes and methods for storing and retrieving meta data
16 - refactored the stream handling
17 - incorporated all the output code from TextTestRunner
18 - made the output of FAIL and ERROR information more flexible and
19 incorporated the new meta data from TestCase
20 - added a flag called 'explain' to __init__ that controls whether the new '
21 explanation' meta data from TestCase is printed along with tracebacks
24 - delegated all output to _TextTestResult
25 - added 'err' and 'explain' to the __init__ signature to match the changes
29 - added -e and --explain as flags on the command line
31 -- Tavis Rudd <tavis@redonions.net> (Sept 28th, 2001)
33 - _TestTextResult.printErrorList(): print blank line after each traceback
35 -- Mike Orr <mso@oz.net> (Nov 11, 2002)
37 TestCase methods copied from unittest in Python 2.3:
38 - .assertAlmostEqual(first, second, places=7, msg=None): to N decimal places.
39 - .failIfAlmostEqual(first, second, places=7, msg=None)
41 -- Mike Orr (Jan 5, 2004)
44 Below is the original docstring for unittest.
45 ---------------------------------------------------------------------------
46 Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's
47 Smalltalk testing framework.
49 This module contains the core framework classes that form the basis of
50 specific test cases and suites (TestCase, TestSuite etc.), and also a
51 text-based utility class for running the tests and reporting the results
58 class IntegerArithmenticTestCase(unittest.TestCase):
59 def testAdd(self): ## test method names begin 'test*'
60 self.assertEquals((1 + 2), 3)
61 self.assertEquals(0 + 1, 1)
62 def testMultiply(self);
63 self.assertEquals((0 * 10), 0)
64 self.assertEquals((5 * 8), 40)
66 if __name__ == '__main__':
69 Further information is available in the bundled documentation, and from
71 http://pyunit.sourceforge.net/
73 Copyright (c) 1999, 2000, 2001 Steve Purcell
74 This module is free software, and you may redistribute it and/or modify
75 it under the same terms as Python itself, so long as this copyright message
76 and disclaimer are retained in their original form.
78 IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
79 SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
80 THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
83 THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
84 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
85 PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
86 AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
87 SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
90 __author__
= "Steve Purcell"
91 __email__
= "stephen_purcell at yahoo dot com"
92 __revision__
= "$Revision: 1.11 $"[11:-2]
95 ##################################################
107 ##################################################
108 ## CONSTANTS & GLOBALS
113 True, False = (1==1),(1==0)
115 ##############################################################################
116 # Test framework core
117 ##############################################################################
121 """Holder for test result information.
123 Test results are automatically managed by the TestCase and TestSuite
124 classes, and do not need to be explicitly manipulated by writers of tests.
126 Each instance holds the total number of tests run, and collections of
127 failures and errors that occurred among those test runs. The collections
128 contain tuples of (testcase, exceptioninfo), where exceptioninfo is a
129 tuple of values as returned by sys.exc_info().
137 def startTest(self
, test
):
138 "Called when the given test is about to be run"
139 self
.testsRun
= self
.testsRun
+ 1
141 def stopTest(self
, test
):
142 "Called when the given test has been run"
145 def addError(self
, test
, err
):
146 "Called when an error has occurred"
147 self
.errors
.append((test
, err
))
149 def addFailure(self
, test
, err
):
150 "Called when a failure has occurred"
151 self
.failures
.append((test
, err
))
153 def addSuccess(self
, test
):
154 "Called when a test has completed successfully"
157 def wasSuccessful(self
):
158 "Tells whether or not this result was a success"
159 return len(self
.failures
) == len(self
.errors
) == 0
162 "Indicates that the tests should be aborted"
166 return "<%s run=%i errors=%i failures=%i>" % \
167 (self
.__class
__, self
.testsRun
, len(self
.errors
),
171 """A class whose instances are single test cases.
173 By default, the test code itself should be placed in a method named
176 If the fixture may be used for many test cases, create as
177 many test methods as are needed. When instantiating such a TestCase
178 subclass, specify in the constructor arguments the name of the test method
179 that the instance is to execute.
181 Test authors should subclass TestCase for their own tests. Construction
182 and deconstruction of the test's environment ('fixture') can be
183 implemented by overriding the 'setUp' and 'tearDown' methods respectively.
185 If it is necessary to override the __init__ method, the base class
186 __init__ method must always be called. It is important that subclasses
187 should not change the signature of their __init__ method, since instances
188 of the classes are instantiated automatically by parts of the framework
192 # This attribute determines which exception will be raised when
193 # the instance's assertion methods fail; test methods raising this
194 # exception will be deemed to have 'failed' rather than 'errored'
196 failureException
= AssertionError
198 # the name of the fixture. Used for displaying meta data about the test
201 def __init__(self
, methodName
='runTest'):
202 """Create an instance of the class that will use the named test
203 method when executed. Raises a ValueError if the instance does
204 not have a method with the specified name.
206 self
._testMethodName
= methodName
207 self
._setupTestMethod
()
208 self
._setupMetaData
()
210 def _setupTestMethod(self
):
212 self
._testMethod
= getattr(self
, self
._testMethodName
)
213 except AttributeError:
214 raise ValueError, "no such test method in %s: %s" % \
215 (self
.__class
__, self
._testMethodName
)
219 def _setupMetaData(self
):
220 """Setup the default meta data for the test case:
222 - id: self.__class__.__name__ + testMethodName OR self.name + testMethodName
223 - description: 1st line of Class docstring + 1st line of method docstring
224 - explanation: rest of Class docstring + rest of method docstring
229 testDoc
= self
._testMethod
.__doc
__ or '\n'
230 testDocLines
= testDoc
.splitlines()
232 testDescription
= testDocLines
[0].strip()
233 if len(testDocLines
) > 1:
234 testExplanation
= '\n'.join(
235 [ln
.strip() for ln
in testDocLines
[1:]]
240 fixtureDoc
= self
.__doc
__ or '\n'
241 fixtureDocLines
= fixtureDoc
.splitlines()
242 fixtureDescription
= fixtureDocLines
[0].strip()
243 if len(fixtureDocLines
) > 1:
244 fixtureExplanation
= '\n'.join(
245 [ln
.strip() for ln
in fixtureDocLines
[1:]]
248 fixtureExplanation
= ''
251 self
.name
= self
.__class
__
252 self
._id
= "%s.%s" % (self
.name
, self
._testMethodName
)
254 if not fixtureDescription
:
255 self
._description
= testDescription
257 self
._description
= fixtureDescription
+ ', ' + testDescription
259 if not fixtureExplanation
:
260 self
._explanation
= testExplanation
262 self
._explanation
= ['Fixture Explanation:',
263 '--------------------',
270 self
._explanation
= '\n'.join(self
._explanation
)
279 """Returns a one-line description of the test, or None if no
280 description has been provided.
282 The default implementation of this method returns the first line of
283 the specified test method's docstring.
285 return self
._description
287 shortDescription
= describe
289 def setDescription(self
, descr
):
290 self
._description
= descr
293 return self
._explanation
295 def setExplanation(self
, expln
):
296 self
._explanation
= expln
301 "Hook method for setting up the test fixture before exercising it."
304 def run(self
, result
=None):
308 "Hook method for deconstructing the test fixture after testing it."
312 """Run the test without collecting errors in a TestResult"""
319 def defaultTestResult(self
):
322 def __call__(self
, result
=None):
324 result
= self
.defaultTestResult()
326 result
.startTest(self
)
331 result
.addError(self
, self
.__exc
_info
())
338 except self
.failureException
, e
:
339 result
.addFailure(self
, self
.__exc
_info
())
341 result
.addError(self
, self
.__exc
_info
())
345 result
.addError(self
, self
.__exc
_info
())
348 result
.addSuccess(self
)
350 result
.stopTest(self
)
354 def countTestCases(self
):
358 return "%s (%s)" % (self
._testMethodName
, self
.__class
__)
361 return "<%s testMethod=%s>" % \
362 (self
.__class
__, self
._testMethodName
)
364 def __exc_info(self
):
365 """Return a version of sys.exc_info() with the traceback frame
366 minimised; usually the top level of the traceback frame is not
369 exctype
, excvalue
, tb
= sys
.exc_info()
370 if sys
.platform
[:4] == 'java': ## tracebacks look different in Jython
371 return (exctype
, excvalue
, tb
)
374 return (exctype
, excvalue
, tb
)
375 return (exctype
, excvalue
, newtb
)
377 ## methods for use by the test cases
379 def fail(self
, msg
=None):
380 """Fail immediately, with the given message."""
381 raise self
.failureException
, msg
383 def failIf(self
, expr
, msg
=None):
384 "Fail the test if the expression is true."
385 if expr
: raise self
.failureException
, msg
387 def failUnless(self
, expr
, msg
=None):
388 """Fail the test unless the expression is true."""
389 if not expr
: raise self
.failureException
, msg
391 def failUnlessRaises(self
, excClass
, callableObj
, *args
, **kwargs
):
392 """Fail unless an exception of class excClass is thrown
393 by callableObj when invoked with arguments args and keyword
394 arguments kwargs. If a different type of exception is
395 thrown, it will not be caught, and the test case will be
396 deemed to have suffered an error, exactly as for an
397 unexpected exception.
400 apply(callableObj
, args
, kwargs
)
404 if hasattr(excClass
,'__name__'): excName
= excClass
.__name
__
405 else: excName
= str(excClass
)
406 raise self
.failureException
, excName
408 def failUnlessEqual(self
, first
, second
, msg
=None):
409 """Fail if the two objects are unequal as determined by the '!='
413 raise self
.failureException
, (msg
or '%s != %s' % (first
, second
))
415 def failIfEqual(self
, first
, second
, msg
=None):
416 """Fail if the two objects are equal as determined by the '=='
420 raise self
.failureException
, (msg
or '%s == %s' % (first
, second
))
422 def failUnlessAlmostEqual(self
, first
, second
, places
=7, msg
=None):
423 """Fail if the two objects are unequal as determined by their
424 difference rounded to the given number of decimal places
425 (default 7) and comparing to zero.
427 Note that decimal places (from zero) is usually not the same
428 as significant digits (measured from the most signficant digit).
430 if round(second
-first
, places
) != 0:
431 raise self
.failureException
, \
432 (msg
or '%s != %s within %s places' % (`first`
, `second`
, `places`
))
434 def failIfAlmostEqual(self
, first
, second
, places
=7, msg
=None):
435 """Fail if the two objects are equal as determined by their
436 difference rounded to the given number of decimal places
437 (default 7) and comparing to zero.
439 Note that decimal places (from zero) is usually not the same
440 as significant digits (measured from the most signficant digit).
442 if round(second
-first
, places
) == 0:
443 raise self
.failureException
, \
444 (msg
or '%s == %s within %s places' % (`first`
, `second`
, `places`
))
448 assertEqual
= assertEquals
= failUnlessEqual
450 assertNotEqual
= assertNotEquals
= failIfEqual
452 assertAlmostEqual
= assertAlmostEquals
= failUnlessAlmostEqual
454 assertNotAlmostEqual
= assertNotAlmostEquals
= failIfAlmostEqual
456 assertRaises
= failUnlessRaises
461 class FunctionTestCase(TestCase
):
462 """A test case that wraps a test function.
464 This is useful for slipping pre-existing test functions into the
465 PyUnit framework. Optionally, set-up and tidy-up functions can be
466 supplied. As with TestCase, the tidy-up ('tearDown') function will
467 always be called if the set-up ('setUp') function ran successfully.
470 def __init__(self
, testFunc
, setUp
=None, tearDown
=None,
472 TestCase
.__init
__(self
)
473 self
.__setUpFunc
= setUp
474 self
.__tearDownFunc
= tearDown
475 self
.__testFunc
= testFunc
476 self
.__description
= description
479 if self
.__setUpFunc
is not None:
483 if self
.__tearDownFunc
is not None:
484 self
.__tearDownFunc
()
490 return self
.__testFunc
.__name
__
493 return "%s (%s)" % (self
.__class
__, self
.__testFunc
.__name
__)
496 return "<%s testFunc=%s>" % (self
.__class
__, self
.__testFunc
)
500 if self
.__description
is not None: return self
.__description
501 doc
= self
.__testFunc
.__doc
__
502 return doc
and string
.strip(string
.split(doc
, "\n")[0]) or None
505 shortDescription
= describe
508 """A test suite is a composite test consisting of a number of TestCases.
510 For use, create an instance of TestSuite, then add test case instances.
511 When all tests have been added, the suite can be passed to a test
512 runner, such as TextTestRunner. It will run the individual test cases
513 in the order in which they were added, aggregating the results. When
514 subclassing, do not forget to call the base class constructor.
516 def __init__(self
, tests
=(), suiteName
=None):
519 self
.suiteName
= suiteName
523 return "<%s tests=%s>" % (self
.__class
__, pprint
.pformat(self
._tests
))
527 def countTestCases(self
):
529 for test
in self
._tests
:
530 cases
= cases
+ test
.countTestCases()
533 def addTest(self
, test
):
534 self
._tests
.append(test
)
535 if isinstance(test
, TestSuite
) and test
.suiteName
:
536 name
= test
.suiteName
537 elif isinstance(test
, TestCase
):
538 #print test, test._testMethodName
539 name
= test
._testMethodName
541 name
= test
.__class
__.__name
__
542 self
._testMap
[name
] = test
544 def addTests(self
, tests
):
548 def getTestForName(self
, name
):
549 return self
._testMap
[name
]
551 def run(self
, result
):
554 def __call__(self
, result
):
555 for test
in self
._tests
:
556 if result
.shouldStop
:
562 """Run the tests without collecting errors in a TestResult"""
563 for test
in self
._tests
: test
.debug()
566 ##############################################################################
568 ##############################################################################
571 def __init__(self
, out
=sys
.stdout
, err
=sys
.stderr
):
572 self
._streamOut
= out
573 self
._streamErr
= err
575 def write(self
, txt
):
576 self
._streamOut
.write(txt
)
577 self
._streamOut
.flush()
579 def writeln(self
, *lines
):
581 self
.write(line
+ '\n')
585 def writeErr(self
, txt
):
586 self
._streamErr
.write(txt
)
588 def writelnErr(self
, *lines
):
590 self
.writeErr(line
+ '\n')
595 class _TextTestResult(TestResult
, StreamWrapper
):
605 errStream
=sys
.stderr
,
609 TestResult
.__init
__(self
)
610 StreamWrapper
.__init
__(self
, out
=stream
, err
=errStream
)
612 self
._verbosity
= verbosity
613 self
._showAll
= verbosity
> 1
614 self
._dots
= (verbosity
== 1)
615 self
._explain
= explain
617 ## startup and shutdown methods
619 def beginTests(self
):
620 self
._startTime
= time
.time()
623 self
._stopTime
= time
.time()
624 self
._timeTaken
= float(self
._stopTime
- self
._startTime
)
629 ## methods called for each test
631 def startTest(self
, test
):
632 TestResult
.startTest(self
, test
)
634 self
.write("%s (%s)" %( test
.id(), test
.describe() ) )
637 def addSuccess(self
, test
):
638 TestResult
.addSuccess(self
, test
)
644 def addError(self
, test
, err
):
645 TestResult
.addError(self
, test
, err
)
647 self
.writeln("ERROR")
650 if err
[0] is KeyboardInterrupt:
653 def addFailure(self
, test
, err
):
654 TestResult
.addFailure(self
, test
, err
)
666 self
.writeln("Ran %d test%s in %.3fs" %
667 (run
, run
== 1 and "" or "s", self
._timeTaken
))
669 if not self
.wasSuccessful():
670 self
.writeErr("FAILED (")
671 failed
, errored
= map(len, (self
.failures
, self
.errors
))
673 self
.writeErr("failures=%d" % failed
)
675 if failed
: self
.writeErr(", ")
676 self
.writeErr("errors=%d" % errored
)
679 self
.writelnErr("OK")
682 self
.writeln(self
._sep
1 * self
._separatorWidth
)
685 self
.writeln(self
._sep
2 * self
._separatorWidth
)
687 def writeErrSep1(self
):
688 self
.writeln(self
._errorSep
1 * self
._separatorWidth
)
690 def writeErrSep2(self
):
691 self
.writeln(self
._errorSep
2 * self
._separatorWidth
)
693 def printErrors(self
):
694 if self
._dots
or self
._showAll
:
696 self
.printErrorList('ERROR', self
.errors
)
697 self
.printErrorList('FAIL', self
.failures
)
699 def printErrorList(self
, flavour
, errors
):
700 for test
, err
in errors
:
702 self
.writelnErr("%s %s (%s)" % (flavour
, test
.id(), test
.describe() ))
704 expln
= test
.explain()
707 self
.writeErr( expln
)
711 for line
in apply(traceback
.format_exception
, err
):
712 for l
in line
.split("\n")[:-1]:
716 class TextTestRunner
:
719 errStream
=sys
.stderr
,
724 self
._err
= errStream
725 self
._verbosity
= verbosity
726 self
._explain
= explain
731 result
= self
._makeResult
()
741 def _makeResult(self
):
742 return _TextTestResult(stream
=self
._out
,
744 verbosity
=self
._verbosity
,
745 explain
=self
._explain
,
748 ##############################################################################
749 # Locating and loading tests
750 ##############################################################################
753 """This class is responsible for loading tests according to various
754 criteria and returning them wrapped in a Test
756 testMethodPrefix
= 'test'
757 sortTestMethodsUsing
= cmp
758 suiteClass
= TestSuite
760 def loadTestsFromTestCase(self
, testCaseClass
):
761 """Return a suite of all tests cases contained in testCaseClass"""
762 return self
.suiteClass(tests
=map(testCaseClass
,
763 self
.getTestCaseNames(testCaseClass
)),
764 suiteName
=testCaseClass
.__name
__)
766 def loadTestsFromModule(self
, module
):
767 """Return a suite of all tests cases contained in the given module"""
769 for name
in dir(module
):
770 obj
= getattr(module
, name
)
771 if type(obj
) == types
.ClassType
and issubclass(obj
, TestCase
):
772 tests
.append(self
.loadTestsFromTestCase(obj
))
773 return self
.suiteClass(tests
)
775 def loadTestsFromName(self
, name
, module
=None):
776 """Return a suite of all tests cases given a string specifier.
778 The name may resolve either to a module, a test case class, a
779 test method within a test case class, or a callable object which
780 returns a TestCase or TestSuite instance.
782 The method optionally resolves the names relative to a given module.
784 parts
= string
.split(name
, '.')
787 raise ValueError, "incomplete test name: %s" % name
789 parts_copy
= parts
[:]
792 module
= __import__(string
.join(parts_copy
,'.'))
796 if not parts_copy
: raise
800 if isinstance(obj
, TestSuite
):
801 obj
= obj
.getTestForName(part
)
803 obj
= getattr(obj
, part
)
805 if type(obj
) == types
.ModuleType
:
806 return self
.loadTestsFromModule(obj
)
807 elif type(obj
) == types
.ClassType
and issubclass(obj
, TestCase
):
808 return self
.loadTestsFromTestCase(obj
)
809 elif type(obj
) == types
.UnboundMethodType
:
810 return obj
.im_class(obj
.__name
__)
811 elif isinstance(obj
, TestSuite
):
813 elif isinstance(obj
, TestCase
):
817 if not isinstance(test
, TestCase
) and \
818 not isinstance(test
, TestSuite
):
820 "calling %s returned %s, not a test" %(obj
,test
)
823 raise ValueError, "don't know how to make test from: %s" % obj
825 def loadTestsFromNames(self
, names
, module
=None):
826 """Return a suite of all tests cases found using the given sequence
827 of string specifiers. See 'loadTestsFromName()'.
831 suites
.append(self
.loadTestsFromName(name
, module
))
832 return self
.suiteClass(suites
)
834 def getTestCaseNames(self
, testCaseClass
):
835 """Return a sorted sequence of method names found within testCaseClass.
837 testFnNames
= filter(lambda n
,p
=self
.testMethodPrefix
: n
[:len(p
)] == p
,
839 for baseclass
in testCaseClass
.__bases
__:
840 for testFnName
in self
.getTestCaseNames(baseclass
):
841 if testFnName
not in testFnNames
: # handle overridden methods
842 testFnNames
.append(testFnName
)
843 if self
.sortTestMethodsUsing
:
844 testFnNames
.sort(self
.sortTestMethodsUsing
)
849 defaultTestLoader
= TestLoader()
852 ##############################################################################
853 # Patches for old functions: these functions should be considered obsolete
854 ##############################################################################
856 def _makeLoader(prefix
, sortUsing
, suiteClass
=None):
857 loader
= TestLoader()
858 loader
.sortTestMethodsUsing
= sortUsing
859 loader
.testMethodPrefix
= prefix
860 if suiteClass
: loader
.suiteClass
= suiteClass
863 def getTestCaseNames(testCaseClass
, prefix
, sortUsing
=cmp):
864 return _makeLoader(prefix
, sortUsing
).getTestCaseNames(testCaseClass
)
866 def makeSuite(testCaseClass
, prefix
='test', sortUsing
=cmp, suiteClass
=TestSuite
):
867 return _makeLoader(prefix
, sortUsing
, suiteClass
).loadTestsFromTestCase(testCaseClass
)
869 def findTestCases(module
, prefix
='test', sortUsing
=cmp, suiteClass
=TestSuite
):
870 return _makeLoader(prefix
, sortUsing
, suiteClass
).loadTestsFromModule(module
)
872 ##############################################################################
873 # Facilities for running tests from the command line
874 ##############################################################################
877 """A command-line program that runs a set of tests; this is primarily
878 for making test modules conveniently executable.
881 Usage: %(progName)s [options] [test] [...]
884 -h, --help Show this message
885 -v, --verbose Verbose output
886 -q, --quiet Minimal output
887 -e, --expain Output extra test details if there is a failure or error
890 %(progName)s - run default set of tests
891 %(progName)s MyTestSuite - run suite 'MyTestSuite'
892 %(progName)s MyTestSuite.MyTestCase - run suite 'MyTestSuite'
893 %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething
894 %(progName)s MyTestCase - run all 'test*' test methods
897 def __init__(self
, module
='__main__', defaultTest
=None,
898 argv
=None, testRunner
=None, testLoader
=defaultTestLoader
,
900 if type(module
) == type(''):
901 self
.module
= __import__(module
)
902 for part
in string
.split(module
,'.')[1:]:
903 self
.module
= getattr(self
.module
, part
)
908 self
.test
= testSuite
911 self
.defaultTest
= defaultTest
912 self
.testRunner
= testRunner
913 self
.testLoader
= testLoader
914 self
.progName
= os
.path
.basename(argv
[0])
918 def usageExit(self
, msg
=None):
920 print self
.USAGE
% self
.__dict
__
923 def parseArgs(self
, argv
):
926 options
, args
= getopt
.getopt(argv
[1:], 'hHvqer',
927 ['help','verbose','quiet','explain', 'raise'])
928 for opt
, value
in options
:
929 if opt
in ('-h','-H','--help'):
931 if opt
in ('-q','--quiet'):
933 if opt
in ('-v','--verbose'):
935 if opt
in ('-e','--explain'):
937 if len(args
) == 0 and self
.defaultTest
is None and self
.test
is None:
938 self
.test
= self
.testLoader
.loadTestsFromModule(self
.module
)
941 self
.testNames
= args
943 self
.testNames
= (self
.defaultTest
,)
945 except getopt
.error
, msg
:
948 def createTests(self
):
949 if self
.test
== None:
950 self
.test
= self
.testLoader
.loadTestsFromNames(self
.testNames
,
954 if self
.testRunner
is None:
955 self
.testRunner
= TextTestRunner(verbosity
=self
.verbosity
,
956 explain
=self
.explain
)
957 result
= self
.testRunner
.run(self
.test
)
958 self
._cleanupAfterRunningTests
()
959 sys
.exit(not result
.wasSuccessful())
961 def _cleanupAfterRunningTests(self
):
962 """A hook method that is called immediately prior to calling
963 sys.exit(not result.wasSuccessful()) in self.runTests().
970 ##############################################################################
971 # Executing this module from the command line
972 ##############################################################################
974 if __name__
== "__main__":
977 # vim: shiftwidth=4 tabstop=4 expandtab