Commit 15d8927f authored by Steve Purcell's avatar Steve Purcell

- New fail*() methods, and comprehensive set of assert*() synonyms

- TestCase.failureException defines the exception that indicates a test failure
- Docstrings for TestLoader class
- Added exc_info() hack back in
parent 5c9aad60
...@@ -131,6 +131,13 @@ class TestCase: ...@@ -131,6 +131,13 @@ class TestCase:
of the classes are instantiated automatically by parts of the framework of the classes are instantiated automatically by parts of the framework
in order to be run. in order to be run.
""" """
# This attribute determines which exception will be raised when
# the instance's assertion methods fail; test methods raising this
# exception will be deemed to have 'failed' rather than 'errored'
failureException = AssertionError
def __init__(self, methodName='runTest'): def __init__(self, methodName='runTest'):
"""Create an instance of the class that will use the named test """Create an instance of the class that will use the named test
method when executed. Raises a ValueError if the instance does method when executed. Raises a ValueError if the instance does
...@@ -189,22 +196,22 @@ class TestCase: ...@@ -189,22 +196,22 @@ class TestCase:
try: try:
self.setUp() self.setUp()
except: except:
result.addError(self,sys.exc_info()) result.addError(self,self.__exc_info())
return return
ok = 0 ok = 0
try: try:
testMethod() testMethod()
ok = 1 ok = 1
except AssertionError, e: except self.failureException, e:
result.addFailure(self,sys.exc_info()) result.addFailure(self,self.__exc_info())
except: except:
result.addError(self,sys.exc_info()) result.addError(self,self.__exc_info())
try: try:
self.tearDown() self.tearDown()
except: except:
result.addError(self,sys.exc_info()) result.addError(self,self.__exc_info())
ok = 0 ok = 0
if ok: result.addSuccess(self) if ok: result.addSuccess(self)
finally: finally:
...@@ -216,21 +223,33 @@ class TestCase: ...@@ -216,21 +223,33 @@ class TestCase:
getattr(self, self.__testMethodName)() getattr(self, self.__testMethodName)()
self.tearDown() self.tearDown()
def assert_(self, expr, msg=None): def __exc_info(self):
"""Equivalent of built-in 'assert', but is not optimised out when """Return a version of sys.exc_info() with the traceback frame
__debug__ is false. minimised; usually the top level of the traceback frame is not
needed.
""" """
if not expr: exctype, excvalue, tb = sys.exc_info()
raise AssertionError, msg if sys.platform[:4] == 'java': ## tracebacks look different in Jython
return (exctype, excvalue, tb)
newtb = tb.tb_next
if newtb is None:
return (exctype, excvalue, tb)
return (exctype, excvalue, newtb)
failUnless = assert_ def fail(self, msg=None):
"""Fail immediately, with the given message."""
raise self.failureException, msg
def failIf(self, expr, msg=None): def failIf(self, expr, msg=None):
"Fail the test if the expression is true." "Fail the test if the expression is true."
apply(self.assert_,(not expr,msg)) if expr: raise self.failureException, msg
def failUnless(self, expr, msg=None):
"""Fail the test unless the expression is true."""
if not expr: raise self.failureException, msg
def assertRaises(self, excClass, callableObj, *args, **kwargs): def failUnlessRaises(self, excClass, callableObj, *args, **kwargs):
"""Assert that an exception of class excClass is thrown """Fail unless an exception of class excClass is thrown
by callableObj when invoked with arguments args and keyword by callableObj when invoked with arguments args and keyword
arguments kwargs. If a different type of exception is arguments kwargs. If a different type of exception is
thrown, it will not be caught, and the test case will be thrown, it will not be caught, and the test case will be
...@@ -244,27 +263,30 @@ class TestCase: ...@@ -244,27 +263,30 @@ class TestCase:
else: else:
if hasattr(excClass,'__name__'): excName = excClass.__name__ if hasattr(excClass,'__name__'): excName = excClass.__name__
else: excName = str(excClass) else: excName = str(excClass)
raise AssertionError, excName raise self.failureException, excName
def assertEquals(self, first, second, msg=None): def failUnlessEqual(self, first, second, msg=None):
"""Assert that the two objects are equal as determined by the '==' """Fail if the two objects are unequal as determined by the '!='
operator. operator.
""" """
self.assert_((first == second), msg or '%s != %s' % (first, second)) if first != second:
raise self.failureException, (msg or '%s != %s' % (first, second))
def assertNotEquals(self, first, second, msg=None): def failIfEqual(self, first, second, msg=None):
"""Assert that the two objects are unequal as determined by the '!=' """Fail if the two objects are equal as determined by the '=='
operator. operator.
""" """
self.assert_((first != second), msg or '%s == %s' % (first, second)) if first == second:
raise self.failureException, (msg or '%s != %s' % (first, second))
assertEqual = assertEquals assertEqual = assertEquals = failUnlessEqual
assertNotEqual = assertNotEquals assertNotEqual = assertNotEquals = failIfEqual
assertRaises = failUnlessRaises
assert_ = failUnless
def fail(self, msg=None):
"""Fail immediately, with the given message."""
raise AssertionError, msg
class TestSuite: class TestSuite:
...@@ -364,18 +386,18 @@ class FunctionTestCase(TestCase): ...@@ -364,18 +386,18 @@ class FunctionTestCase(TestCase):
class TestLoader: class TestLoader:
"""This class is responsible for loading tests according to various """This class is responsible for loading tests according to various
criteria and returning them wrapped in a Test criteria and returning them wrapped in a Test
It can load all tests within a given, module
""" """
testMethodPrefix = 'test' testMethodPrefix = 'test'
sortTestMethodsUsing = cmp sortTestMethodsUsing = cmp
suiteClass = TestSuite suiteClass = TestSuite
def loadTestsFromTestCase(self, testCaseClass): def loadTestsFromTestCase(self, testCaseClass):
"""Return a suite of all tests cases contained in testCaseClass"""
return self.suiteClass(map(testCaseClass, return self.suiteClass(map(testCaseClass,
self.getTestCaseNames(testCaseClass))) self.getTestCaseNames(testCaseClass)))
def loadTestsFromModule(self, module): def loadTestsFromModule(self, module):
"""Return a suite of all tests cases contained in the given module"""
tests = [] tests = []
for name in dir(module): for name in dir(module):
obj = getattr(module, name) obj = getattr(module, name)
...@@ -384,6 +406,14 @@ class TestLoader: ...@@ -384,6 +406,14 @@ class TestLoader:
return self.suiteClass(tests) return self.suiteClass(tests)
def loadTestsFromName(self, name, module=None): def loadTestsFromName(self, name, module=None):
"""Return a suite of all tests cases given a string specifier.
The name may resolve either to a module, a test case class, a
test method within a test case class, or a callable object which
returns a TestCase or TestSuite instance.
The method optionally resolves the names relative to a given module.
"""
parts = string.split(name, '.') parts = string.split(name, '.')
if module is None: if module is None:
if not parts: if not parts:
...@@ -419,12 +449,17 @@ class TestLoader: ...@@ -419,12 +449,17 @@ class TestLoader:
raise ValueError, "don't know how to make test from: %s" % obj raise ValueError, "don't know how to make test from: %s" % obj
def loadTestsFromNames(self, names, module=None): def loadTestsFromNames(self, names, module=None):
"""Return a suite of all tests cases found using the given sequence
of string specifiers. See 'loadTestsFromName()'.
"""
suites = [] suites = []
for name in names: for name in names:
suites.append(self.loadTestsFromName(name, module)) suites.append(self.loadTestsFromName(name, module))
return self.suiteClass(suites) return self.suiteClass(suites)
def getTestCaseNames(self, testCaseClass): def getTestCaseNames(self, testCaseClass):
"""Return a sorted sequence of method names found within testCaseClass
"""
testFnNames = filter(lambda n,p=self.testMethodPrefix: n[:len(p)] == p, testFnNames = filter(lambda n,p=self.testMethodPrefix: n[:len(p)] == p,
dir(testCaseClass)) dir(testCaseClass))
for baseclass in testCaseClass.__bases__: for baseclass in testCaseClass.__bases__:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment