summaryrefslogtreecommitdiffstats
path: root/Lib/unittest.py
diff options
context:
space:
mode:
authorSteve Purcell <steve@pythonconsulting.com>2001-03-22 08:45:36 (GMT)
committerSteve Purcell <steve@pythonconsulting.com>2001-03-22 08:45:36 (GMT)
commit5ddd1a8dcb260eb8c25d7d27f7623f020648ad70 (patch)
tree304eb67a092f226980deaeb6c7b2351bed1e97ec /Lib/unittest.py
parent2e2cded1b56cc5488f49d395b46131fd995b02bc (diff)
downloadcpython-5ddd1a8dcb260eb8c25d7d27f7623f020648ad70.zip
cpython-5ddd1a8dcb260eb8c25d7d27f7623f020648ad70.tar.gz
cpython-5ddd1a8dcb260eb8c25d7d27f7623f020648ad70.tar.bz2
Updated to latest PyUnit version (1.31 in PyUnit CVS); test_support.py
changed accordingly.
Diffstat (limited to 'Lib/unittest.py')
-rw-r--r--Lib/unittest.py484
1 files changed, 238 insertions, 246 deletions
diff --git a/Lib/unittest.py b/Lib/unittest.py
index b3eec18..850c38e 100644
--- a/Lib/unittest.py
+++ b/Lib/unittest.py
@@ -1,17 +1,32 @@
#!/usr/bin/env python
-"""
+'''
Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's
Smalltalk testing framework.
-Further information is available in the bundled documentation, and from
-
- http://pyunit.sourceforge.net/
-
This module contains the core framework classes that form the basis of
specific test cases and suites (TestCase, TestSuite etc.), and also a
text-based utility class for running the tests and reporting the results
(TextTestRunner).
+Simple usage:
+
+ import unittest
+
+ class IntegerArithmenticTestCase(unittest.TestCase):
+ def testAdd(self): ## test method names begin 'test*'
+ self.assertEquals((1 + 2), 3)
+ self.assertEquals(0 + 1, 1)
+ def testMultiply(self);
+ self.assertEquals((0 * 10), 0)
+ self.assertEquals((5 * 8), 40)
+
+ if __name__ == '__main__':
+ unittest.main()
+
+Further information is available in the bundled documentation, and from
+
+ http://pyunit.sourceforge.net/
+
Copyright (c) 1999, 2000, 2001 Steve Purcell
This module is free software, and you may redistribute it and/or modify
it under the same terms as Python itself, so long as this copyright message
@@ -27,9 +42,10 @@ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
-"""
+'''
-__author__ = "Steve Purcell (stephen_purcell@yahoo.com)"
+__author__ = "Steve Purcell"
+__email__ = "stephen_purcell at yahoo dot com"
__version__ = "$Revision$"[11:-2]
import time
@@ -37,15 +53,7 @@ import sys
import traceback
import string
import os
-
-##############################################################################
-# A platform-specific concession to help the code work for JPython users
-##############################################################################
-
-plat = string.lower(sys.platform)
-_isJPython = string.find(plat, 'java') >= 0 or string.find(plat, 'jdk') >= 0
-del plat
-
+import types
##############################################################################
# Test framework core
@@ -84,6 +92,10 @@ class TestResult:
"Called when a failure has occurred"
self.failures.append((test, err))
+ def addSuccess(self, test):
+ "Called when a test has completed successfully"
+ pass
+
def wasSuccessful(self):
"Tells whether or not this result was a success"
return len(self.failures) == len(self.errors) == 0
@@ -101,17 +113,23 @@ class TestResult:
class TestCase:
"""A class whose instances are single test cases.
- Test authors should subclass TestCase for their own tests. Construction
- and deconstruction of the test's environment ('fixture') can be
- implemented by overriding the 'setUp' and 'tearDown' methods respectively.
-
By default, the test code itself should be placed in a method named
'runTest'.
-
+
If the fixture may be used for many test cases, create as
many test methods as are needed. When instantiating such a TestCase
subclass, specify in the constructor arguments the name of the test method
that the instance is to execute.
+
+ Test authors should subclass TestCase for their own tests. Construction
+ and deconstruction of the test's environment ('fixture') can be
+ implemented by overriding the 'setUp' and 'tearDown' methods respectively.
+
+ If it is necessary to override the __init__ method, the base class
+ __init__ method must always be called. It is important that subclasses
+ should not change the signature of their __init__ method, since instances
+ of the classes are instantiated automatically by parts of the framework
+ in order to be run.
"""
def __init__(self, methodName='runTest'):
"""Create an instance of the class that will use the named test
@@ -119,7 +137,9 @@ class TestCase:
not have a method with the specified name.
"""
try:
- self.__testMethod = getattr(self,methodName)
+ self.__testMethodName = methodName
+ testMethod = getattr(self, methodName)
+ self.__testMethodDoc = testMethod.__doc__
except AttributeError:
raise ValueError, "no such test method in %s: %s" % \
(self.__class__, methodName)
@@ -145,18 +165,18 @@ class TestCase:
The default implementation of this method returns the first line of
the specified test method's docstring.
"""
- doc = self.__testMethod.__doc__
+ doc = self.__testMethodDoc
return doc and string.strip(string.split(doc, "\n")[0]) or None
def id(self):
- return "%s.%s" % (self.__class__, self.__testMethod.__name__)
+ return "%s.%s" % (self.__class__, self.__testMethodName)
def __str__(self):
- return "%s (%s)" % (self.__testMethod.__name__, self.__class__)
+ return "%s (%s)" % (self.__testMethodName, self.__class__)
def __repr__(self):
return "<%s testMethod=%s>" % \
- (self.__class__, self.__testMethod.__name__)
+ (self.__class__, self.__testMethodName)
def run(self, result=None):
return self(result)
@@ -164,6 +184,7 @@ class TestCase:
def __call__(self, result=None):
if result is None: result = self.defaultTestResult()
result.startTest(self)
+ testMethod = getattr(self, self.__testMethodName)
try:
try:
self.setUp()
@@ -171,8 +192,10 @@ class TestCase:
result.addError(self,self.__exc_info())
return
+ ok = 0
try:
- self.__testMethod()
+ testMethod()
+ ok = 1
except AssertionError, e:
result.addFailure(self,self.__exc_info())
except:
@@ -182,12 +205,15 @@ class TestCase:
self.tearDown()
except:
result.addError(self,self.__exc_info())
+ ok = 0
+ if ok: result.addSuccess(self)
finally:
result.stopTest(self)
def debug(self):
+ """Run the test without collecting errors in a TestResult"""
self.setUp()
- self.__testMethod()
+ getattr(self, self.__testMethodName)()
self.tearDown()
def assert_(self, expr, msg=None):
@@ -220,10 +246,26 @@ class TestCase:
else: excName = str(excClass)
raise AssertionError, excName
+ def assertEquals(self, first, second, msg=None):
+ """Assert that the two objects are equal as determined by the '=='
+ operator.
+ """
+ self.assert_((first == second), msg or '%s != %s' % (first, second))
+
+ def assertNotEquals(self, first, second, msg=None):
+ """Assert that the two objects are unequal as determined by the '!='
+ operator.
+ """
+ self.assert_((first != second), msg or '%s == %s' % (first, second))
+
+ assertEqual = assertEquals
+
+ assertNotEqual = assertNotEquals
+
def fail(self, msg=None):
"""Fail immediately, with the given message."""
raise AssertionError, msg
-
+
def __exc_info(self):
"""Return a version of sys.exc_info() with the traceback frame
minimised; usually the top level of the traceback frame is not
@@ -278,8 +320,8 @@ class TestSuite:
return result
def debug(self):
+ """Run the tests without collecting errors in a TestResult"""
for test in self._tests: test.debug()
-
class FunctionTestCase(TestCase):
@@ -327,84 +369,100 @@ class FunctionTestCase(TestCase):
##############################################################################
-# Convenience functions
+# Locating and loading tests
##############################################################################
-def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp):
- """Extracts all the names of functions in the given test case class
- and its base classes that start with the given prefix. This is used
- by makeSuite().
- """
- testFnNames = filter(lambda n,p=prefix: n[:len(p)] == p,
- dir(testCaseClass))
- for baseclass in testCaseClass.__bases__:
- testFnNames = testFnNames + \
- getTestCaseNames(baseclass, prefix, sortUsing=None)
- if sortUsing:
- testFnNames.sort(sortUsing)
- return testFnNames
-
-
-def makeSuite(testCaseClass, prefix='test', sortUsing=cmp):
- """Returns a TestSuite instance built from all of the test functions
- in the given test case class whose names begin with the given
- prefix. The cases are sorted by their function names
- using the supplied comparison function, which defaults to 'cmp'.
+class TestLoader:
+ """This class is responsible for loading tests according to various
+ criteria and returning them wrapped in a Test
+
+ It can load all tests within a given, module
"""
- cases = map(testCaseClass,
- getTestCaseNames(testCaseClass, prefix, sortUsing))
- return TestSuite(cases)
+ testMethodPrefix = 'test'
+ sortTestMethodsUsing = cmp
+ suiteClass = TestSuite
+ def loadTestsFromTestCase(self, testCaseClass):
+ return self.suiteClass(map(testCaseClass,
+ self.getTestCaseNames(testCaseClass)))
-def createTestInstance(name, module=None):
- """Finds tests by their name, optionally only within the given module.
+ def loadTestsFromModule(self, module):
+ tests = []
+ for name in dir(module):
+ obj = getattr(module, name)
+ if type(obj) == types.ClassType and issubclass(obj, TestCase):
+ tests.append(self.loadTestsFromTestCase(obj))
+ return self.suiteClass(tests)
+
+ def loadTestsFromName(self, name, module=None):
+ parts = string.split(name, '.')
+ if module is None:
+ if not parts:
+ raise ValueError, "incomplete test name: %s" % name
+ else:
+ module = __import__(parts)
+ parts = parts[1:]
+ obj = module
+ for part in parts:
+ obj = getattr(obj, part)
+
+ if type(obj) == types.ModuleType:
+ return self.loadTestsFromModule(obj)
+ elif type(obj) == types.ClassType and issubclass(obj, TestCase):
+ return self.loadTestsFromTestCase(obj)
+ elif type(obj) == types.UnboundMethodType:
+ return obj.im_class(obj.__name__)
+ elif callable(obj):
+ test = obj()
+ if not isinstance(test, TestCase) and \
+ not isinstance(test, TestSuite):
+ raise ValueError, \
+ "calling %s returned %s, not a test" % obj,test
+ return test
+ else:
+ raise ValueError, "don't know how to make test from: %s" % obj
- Return the newly-constructed test, ready to run. If the name contains a ':'
- then the portion of the name after the colon is used to find a specific
- test case within the test case class named before the colon.
+ def loadTestsFromNames(self, names, module=None):
+ suites = []
+ for name in names:
+ suites.append(self.loadTestsFromName(name, module))
+ return self.suiteClass(suites)
- Examples:
- findTest('examples.listtests.suite')
- -- returns result of calling 'suite'
- findTest('examples.listtests.ListTestCase:checkAppend')
- -- returns result of calling ListTestCase('checkAppend')
- findTest('examples.listtests.ListTestCase:check-')
- -- returns result of calling makeSuite(ListTestCase, prefix="check")
- """
-
- spec = string.split(name, ':')
- if len(spec) > 2: raise ValueError, "illegal test name: %s" % name
- if len(spec) == 1:
- testName = spec[0]
- caseName = None
- else:
- testName, caseName = spec
- parts = string.split(testName, '.')
- if module is None:
- if len(parts) < 2:
- raise ValueError, "incomplete test name: %s" % name
- constructor = __import__(string.join(parts[:-1],'.'))
- parts = parts[1:]
- else:
- constructor = module
- for part in parts:
- constructor = getattr(constructor, part)
- if not callable(constructor):
- raise ValueError, "%s is not a callable object" % constructor
- if caseName:
- if caseName[-1] == '-':
- prefix = caseName[:-1]
- if not prefix:
- raise ValueError, "prefix too short: %s" % name
- test = makeSuite(constructor, prefix=prefix)
- else:
- test = constructor(caseName)
- else:
- test = constructor()
- if not hasattr(test,"countTestCases"):
- raise TypeError, \
- "object %s found with spec %s is not a test" % (test, name)
- return test
+ def getTestCaseNames(self, testCaseClass):
+ testFnNames = filter(lambda n,p=self.testMethodPrefix: n[:len(p)] == p,
+ dir(testCaseClass))
+ for baseclass in testCaseClass.__bases__:
+ for testFnName in self.getTestCaseNames(baseclass):
+ if testFnName not in testFnNames: # handle overridden methods
+ testFnNames.append(testFnName)
+ if self.sortTestMethodsUsing:
+ testFnNames.sort(self.sortTestMethodsUsing)
+ return testFnNames
+
+
+
+defaultTestLoader = TestLoader()
+
+
+##############################################################################
+# Patches for old functions: these functions should be considered obsolete
+##############################################################################
+
+def _makeLoader(prefix, sortUsing, suiteClass=None):
+ loader = TestLoader()
+ loader.sortTestMethodsUsing = sortUsing
+ loader.testMethodPrefix = prefix
+ if suiteClass: loader.suiteClass = suiteClass
+ return loader
+
+def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp):
+ return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass)
+
+def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, suiteClass=TestSuite):
+ return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass)
+
+def findTestCases(module, prefix='test', sortUsing=cmp, suiteClass=TestSuite):
+ return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module)
##############################################################################
@@ -415,181 +473,107 @@ class _WritelnDecorator:
"""Used to decorate file-like objects with a handy 'writeln' method"""
def __init__(self,stream):
self.stream = stream
- if _isJPython:
- import java.lang.System
- self.linesep = java.lang.System.getProperty("line.separator")
- else:
- self.linesep = os.linesep
def __getattr__(self, attr):
return getattr(self.stream,attr)
def writeln(self, *args):
if args: apply(self.write, args)
- self.write(self.linesep)
-
-
-class _JUnitTextTestResult(TestResult):
- """A test result class that can print formatted text results to a stream.
-
- Used by JUnitTextTestRunner.
- """
- def __init__(self, stream):
- self.stream = stream
- TestResult.__init__(self)
-
- def addError(self, test, error):
- TestResult.addError(self,test,error)
- self.stream.write('E')
- self.stream.flush()
- if error[0] is KeyboardInterrupt:
- self.shouldStop = 1
+ self.write('\n') # text-mode streams translate to \r\n if needed
- def addFailure(self, test, error):
- TestResult.addFailure(self,test,error)
- self.stream.write('F')
- self.stream.flush()
-
- def startTest(self, test):
- TestResult.startTest(self,test)
- self.stream.write('.')
- self.stream.flush()
-
- def printNumberedErrors(self,errFlavour,errors):
- if not errors: return
- if len(errors) == 1:
- self.stream.writeln("There was 1 %s:" % errFlavour)
- else:
- self.stream.writeln("There were %i %ss:" %
- (len(errors), errFlavour))
- i = 1
- for test,error in errors:
- errString = string.join(apply(traceback.format_exception,error),"")
- self.stream.writeln("%i) %s" % (i, test))
- self.stream.writeln(errString)
- i = i + 1
-
- def printErrors(self):
- self.printNumberedErrors("error",self.errors)
-
- def printFailures(self):
- self.printNumberedErrors("failure",self.failures)
-
- def printHeader(self):
- self.stream.writeln()
- if self.wasSuccessful():
- self.stream.writeln("OK (%i tests)" % self.testsRun)
- else:
- self.stream.writeln("!!!FAILURES!!!")
- self.stream.writeln("Test Results")
- self.stream.writeln()
- self.stream.writeln("Run: %i ; Failures: %i ; Errors: %i" %
- (self.testsRun, len(self.failures),
- len(self.errors)))
-
- def printResult(self):
- self.printHeader()
- self.printErrors()
- self.printFailures()
-
-class JUnitTextTestRunner:
- """A test runner class that displays results in textual form.
-
- The display format approximates that of JUnit's 'textui' test runner.
- This test runner may be removed in a future version of PyUnit.
- """
- def __init__(self, stream=sys.stderr):
- self.stream = _WritelnDecorator(stream)
-
- def run(self, test):
- "Run the given test case or test suite."
- result = _JUnitTextTestResult(self.stream)
- startTime = time.time()
- test(result)
- stopTime = time.time()
- self.stream.writeln()
- self.stream.writeln("Time: %.3fs" % float(stopTime - startTime))
- result.printResult()
- return result
-
-
-##############################################################################
-# Verbose text UI
-##############################################################################
-
-class _VerboseTextTestResult(TestResult):
+class _TextTestResult(TestResult):
"""A test result class that can print formatted text results to a stream.
- Used by VerboseTextTestRunner.
+ Used by TextTestRunner.
"""
- def __init__(self, stream, descriptions):
+ separator1 = '=' * 70
+ separator2 = '-' * 70
+
+ def __init__(self, stream, descriptions, verbosity):
TestResult.__init__(self)
self.stream = stream
- self.lastFailure = None
+ self.showAll = verbosity > 1
+ self.dots = verbosity == 1
self.descriptions = descriptions
-
- def startTest(self, test):
- TestResult.startTest(self, test)
+
+ def getDescription(self, test):
if self.descriptions:
- self.stream.write(test.shortDescription() or str(test))
+ return test.shortDescription() or str(test)
else:
- self.stream.write(str(test))
- self.stream.write(" ... ")
+ return str(test)
- def stopTest(self, test):
- TestResult.stopTest(self, test)
- if self.lastFailure is not test:
+ def startTest(self, test):
+ TestResult.startTest(self, test)
+ if self.showAll:
+ self.stream.write(self.getDescription(test))
+ self.stream.write(" ... ")
+
+ def addSuccess(self, test):
+ TestResult.addSuccess(self, test)
+ if self.showAll:
self.stream.writeln("ok")
+ elif self.dots:
+ self.stream.write('.')
def addError(self, test, err):
TestResult.addError(self, test, err)
- self._printError("ERROR", test, err)
- self.lastFailure = test
+ if self.showAll:
+ self.stream.writeln("ERROR")
+ elif self.dots:
+ self.stream.write('E')
if err[0] is KeyboardInterrupt:
self.shouldStop = 1
def addFailure(self, test, err):
TestResult.addFailure(self, test, err)
- self._printError("FAIL", test, err)
- self.lastFailure = test
-
- def _printError(self, flavour, test, err):
- errLines = []
- separator1 = "\t" + '=' * 70
- separator2 = "\t" + '-' * 70
- if not self.lastFailure is test:
+ if self.showAll:
+ self.stream.writeln("FAIL")
+ elif self.dots:
+ self.stream.write('F')
+
+ def printErrors(self):
+ if self.dots or self.showAll:
self.stream.writeln()
- self.stream.writeln(separator1)
- self.stream.writeln("\t%s" % flavour)
- self.stream.writeln(separator2)
- for line in apply(traceback.format_exception, err):
- for l in string.split(line,"\n")[:-1]:
- self.stream.writeln("\t%s" % l)
- self.stream.writeln(separator1)
+ self.printErrorList('ERROR', self.errors)
+ self.printErrorList('FAIL', self.failures)
+
+ def printErrorList(self, flavour, errors):
+ for test, err in errors:
+ self.stream.writeln(self.separator1)
+ self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
+ self.stream.writeln(self.separator2)
+ for line in apply(traceback.format_exception, err):
+ for l in string.split(line,"\n")[:-1]:
+ self.stream.writeln("%s" % l)
-class VerboseTextTestRunner:
+class TextTestRunner:
"""A test runner class that displays results in textual form.
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):
+ def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1):
self.stream = _WritelnDecorator(stream)
self.descriptions = descriptions
+ self.verbosity = verbosity
+
+ def _makeResult(self):
+ return _TextTestResult(self.stream, self.descriptions, self.verbosity)
def run(self, test):
"Run the given test case or test suite."
- result = _VerboseTextTestResult(self.stream, self.descriptions)
+ result = self._makeResult()
startTime = time.time()
test(result)
stopTime = time.time()
timeTaken = float(stopTime - startTime)
- self.stream.writeln("-" * 78)
+ result.printErrors()
+ self.stream.writeln(result.separator2)
run = result.testsRun
self.stream.writeln("Ran %d test%s in %.3fs" %
- (run, run > 1 and "s" or "", timeTaken))
+ (run, run == 1 and "" or "s", timeTaken))
self.stream.writeln()
if not result.wasSuccessful():
self.stream.write("FAILED (")
@@ -605,9 +589,6 @@ class VerboseTextTestRunner:
return result
-# Which flavour of TextTestRunner is the default?
-TextTestRunner = VerboseTextTestRunner
-
##############################################################################
# Facilities for running tests from the command line
@@ -618,17 +599,22 @@ class TestProgram:
for making test modules conveniently executable.
"""
USAGE = """\
-Usage: %(progName)s [-h|--help] [test[:(casename|prefix-)]] [...]
+Usage: %(progName)s [options] [test[:(casename|prefix-)]] [...]
+
+Options:
+ -h, --help Show this message
+ -v, --verbose Verbose output
+ -q, --quiet Minimal output
Examples:
%(progName)s - run default set of tests
%(progName)s MyTestSuite - run suite 'MyTestSuite'
- %(progName)s MyTestCase:checkSomething - run MyTestCase.checkSomething
- %(progName)s MyTestCase:check- - run all 'check*' test methods
+ %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething
+ %(progName)s MyTestCase - run all 'test*' test methods
in MyTestCase
"""
def __init__(self, module='__main__', defaultTest=None,
- argv=None, testRunner=None):
+ argv=None, testRunner=None, testLoader=defaultTestLoader):
if type(module) == type(''):
self.module = __import__(module)
for part in string.split(module,'.')[1:]:
@@ -637,11 +623,12 @@ Examples:
self.module = module
if argv is None:
argv = sys.argv
+ self.verbosity = 1
self.defaultTest = defaultTest
self.testRunner = testRunner
+ self.testLoader = testLoader
self.progName = os.path.basename(argv[0])
self.parseArgs(argv)
- self.createTests()
self.runTests()
def usageExit(self, msg=None):
@@ -652,29 +639,34 @@ Examples:
def parseArgs(self, argv):
import getopt
try:
- options, args = getopt.getopt(argv[1:], 'hH', ['help'])
+ options, args = getopt.getopt(argv[1:], 'hHvq',
+ ['help','verbose','quiet'])
opts = {}
for opt, value in options:
if opt in ('-h','-H','--help'):
self.usageExit()
+ if opt in ('-q','--quiet'):
+ self.verbosity = 0
+ if opt in ('-v','--verbose'):
+ self.verbosity = 2
if len(args) == 0 and self.defaultTest is None:
- raise getopt.error, "No default test is defined."
+ self.test = self.testLoader.loadTestsFromModule(self.module)
+ return
if len(args) > 0:
self.testNames = args
else:
self.testNames = (self.defaultTest,)
+ self.createTests()
except getopt.error, msg:
self.usageExit(msg)
def createTests(self):
- tests = []
- for testName in self.testNames:
- tests.append(createTestInstance(testName, self.module))
- self.test = TestSuite(tests)
+ self.test = self.testLoader.loadTestsFromNames(self.testNames,
+ self.module)
def runTests(self):
if self.testRunner is None:
- self.testRunner = TextTestRunner()
+ self.testRunner = TextTestRunner(verbosity=self.verbosity)
result = self.testRunner.run(self.test)
sys.exit(not result.wasSuccessful())