1 # Copyright (c) 2008, 2009 Jonathan M. Lange. See LICENSE for details.
3 """Test case related stuff."""
7 'clone_test_with_new_id',
16 from functools import wraps
24 from testtools import content
25 from testtools.runtest import RunTest
26 from testtools.testresult import TestResult
27 from testtools.utils import advance_iterator
31 # Try to use the python2.7 SkipTest exception for signalling skips.
32 from unittest.case import SkipTest as TestSkipped
34 class TestSkipped(Exception):
35 """Raised within TestCase.run() when a test is skipped."""
39 # Try to use the same exceptions python 2.7 does.
40 from unittest.case import _ExpectedFailure, _UnexpectedSuccess
42 # Oops, not available, make our own.
43 class _UnexpectedSuccess(Exception):
44 """An unexpected success was raised.
46 Note that this exception is private plumbing in testtools' testcase
50 class _ExpectedFailure(Exception):
51 """An expected failure occured.
53 Note that this exception is private plumbing in testtools' testcase
58 class TestCase(unittest.TestCase):
59 """Extensions to the basic TestCase.
61 :ivar exception_handlers: Exceptions to catch from setUp, runTest and
62 tearDown. This list is able to be modified at any time and consists of
63 (exception_class, handler(case, result, exception_value)) pairs.
66 skipException = TestSkipped
68 def __init__(self, *args, **kwargs):
69 """Construct a TestCase.
71 :param testMethod: The name of the method to run.
72 :param runTest: Optional class to use to execute the test. If not
73 supplied testtools.runtest.RunTest is used. The instance to be
74 used is created when run() is invoked, so will be fresh each time.
76 unittest.TestCase.__init__(self, *args, **kwargs)
78 self._unique_id_gen = itertools.count(1)
79 self.__setup_called = False
80 self.__teardown_called = False
82 self.__RunTest = kwargs.get('runTest', RunTest)
83 self.__exception_handlers = []
84 self.exception_handlers = [
85 (self.skipException, self._report_skip),
86 (self.failureException, self._report_failure),
87 (_ExpectedFailure, self._report_expected_failure),
88 (_UnexpectedSuccess, self._report_unexpected_success),
89 (Exception, self._report_error),
92 def __eq__(self, other):
93 eq = getattr(unittest.TestCase, '__eq__', None)
94 if eq is not None and not unittest.TestCase.__eq__(self, other):
96 return self.__dict__ == other.__dict__
99 # We add id to the repr because it makes testing testtools easier.
100 return "<%s id=0x%0x>" % (self.id(), id(self))
102 def addDetail(self, name, content_object):
103 """Add a detail to be reported with this test's outcome.
105 For more details see pydoc testtools.TestResult.
107 :param name: The name to give this detail.
108 :param content_object: The content object for this detail. See
109 testtools.content for more detail.
111 self.__details[name] = content_object
113 def getDetails(self):
114 """Get the details dict that will be reported with this test's outcome.
116 For more details see pydoc testtools.TestResult.
118 return self.__details
120 def shortDescription(self):
123 def skip(self, reason):
124 """Cause this test to be skipped.
126 This raises self.skipException(reason). skipException is raised
127 to permit a skip to be triggered at any point (during setUp or the
128 testMethod itself). The run() method catches skipException and
129 translates that into a call to the result objects addSkip method.
131 :param reason: The reason why the test is being skipped. This must
132 support being cast into a unicode string for reporting.
134 raise self.skipException(reason)
136 def _formatTypes(self, classOrIterable):
137 """Format a class or a bunch of classes for display in an error."""
138 className = getattr(classOrIterable, '__name__', None)
139 if className is None:
140 className = ', '.join(klass.__name__ for klass in classOrIterable)
143 def _runCleanups(self, result):
144 """Run the cleanups that have been added with addCleanup.
146 See the docstring for addCleanup for more information.
148 Returns True if all cleanups ran without error, False otherwise.
151 while self._cleanups:
152 function, arguments, keywordArguments = self._cleanups.pop()
154 function(*arguments, **keywordArguments)
155 except KeyboardInterrupt:
158 self._report_error(self, result, None)
162 def addCleanup(self, function, *arguments, **keywordArguments):
163 """Add a cleanup function to be called after tearDown.
165 Functions added with addCleanup will be called in reverse order of
166 adding after the test method and before tearDown.
168 If a function added with addCleanup raises an exception, the error
169 will be recorded as a test error, and the next cleanup will then be
172 Cleanup functions are always called before a test finishes running,
173 even if setUp is aborted by an exception.
175 self._cleanups.append((function, arguments, keywordArguments))
177 def addOnException(self, handler):
178 """Add a handler to be called when an exception occurs in test code.
180 This handler cannot affect what result methods are called, and is
181 called before any outcome is called on the result object. An example
182 use for it is to add some diagnostic state to the test details dict
183 which is expensive to calculate and not interesting for reporting in
186 Handlers are called before the outcome (such as addFailure) that
187 the exception has caused.
189 Handlers are called in first-added, first-called order, and if they
190 raise an exception, that will propogate out of the test running
191 machinery, halting test processing. As a result, do not call code that
192 may unreasonably fail.
194 self.__exception_handlers.append(handler)
196 def _add_reason(self, reason):
197 self.addDetail('reason', content.Content(
198 content.ContentType('text', 'plain'),
199 lambda: [reason.encode('utf8')]))
201 def assertIn(self, needle, haystack):
202 """Assert that needle is in haystack."""
204 needle in haystack, '%r not in %r' % (needle, haystack))
206 def assertIs(self, expected, observed, message=''):
207 """Assert that 'expected' is 'observed'.
209 :param expected: The expected value.
210 :param observed: The observed value.
211 :param message: An optional message describing the error.
214 message = ': ' + message
216 expected is observed,
217 '%r is not %r%s' % (expected, observed, message))
219 def assertIsNot(self, expected, observed, message=''):
220 """Assert that 'expected' is not 'observed'."""
222 message = ': ' + message
224 expected is not observed,
225 '%r is %r%s' % (expected, observed, message))
227 def assertNotIn(self, needle, haystack):
228 """Assert that needle is not in haystack."""
230 needle not in haystack, '%r in %r' % (needle, haystack))
232 def assertIsInstance(self, obj, klass):
234 isinstance(obj, klass),
235 '%r is not an instance of %s' % (obj, self._formatTypes(klass)))
237 def assertRaises(self, excClass, callableObj, *args, **kwargs):
238 """Fail unless an exception of class excClass is thrown
239 by callableObj when invoked with arguments args and keyword
240 arguments kwargs. If a different type of exception is
241 thrown, it will not be caught, and the test case will be
242 deemed to have suffered an error, exactly as for an
243 unexpected exception.
246 ret = callableObj(*args, **kwargs)
248 return sys.exc_info()[1]
250 excName = self._formatTypes(excClass)
251 self.fail("%s not raised, %r returned instead." % (excName, ret))
252 failUnlessRaises = assertRaises
254 def assertThat(self, matchee, matcher):
255 """Assert that matchee is matched by matcher.
257 :param matchee: An object to match with matcher.
258 :param matcher: An object meeting the testtools.Matcher protocol.
259 :raises self.failureException: When matcher does not match thing.
261 mismatch = matcher.match(matchee)
264 self.fail('Match failed. Matchee: "%s"\nMatcher: %s\nDifference: %s\n'
265 % (matchee, matcher, mismatch.describe()))
267 def defaultTestResult(self):
270 def expectFailure(self, reason, predicate, *args, **kwargs):
271 """Check that a test fails in a particular way.
273 If the test fails in the expected way, a KnownFailure is caused. If it
274 succeeds an UnexpectedSuccess is caused.
276 The expected use of expectFailure is as a barrier at the point in a
277 test where the test would fail. For example:
278 >>> def test_foo(self):
279 >>> self.expectFailure("1 should be 0", self.assertNotEqual, 1, 0)
280 >>> self.assertEqual(1, 0)
282 If in the future 1 were to equal 0, the expectFailure call can simply
283 be removed. This separation preserves the original intent of the test
284 while it is in the expectFailure mode.
286 self._add_reason(reason)
288 predicate(*args, **kwargs)
289 except self.failureException:
290 exc_info = sys.exc_info()
291 self.addDetail('traceback',
292 content.TracebackContent(exc_info, self))
293 raise _ExpectedFailure(exc_info)
295 raise _UnexpectedSuccess(reason)
297 def getUniqueInteger(self):
298 """Get an integer unique to this test.
300 Returns an integer that is guaranteed to be unique to this instance.
301 Use this when you need an arbitrary integer in your test, or as a
302 helper for custom anonymous factory methods.
304 return advance_iterator(self._unique_id_gen)
306 def getUniqueString(self, prefix=None):
307 """Get a string unique to this test.
309 Returns a string that is guaranteed to be unique to this instance. Use
310 this when you need an arbitrary string in your test, or as a helper
311 for custom anonymous factory methods.
313 :param prefix: The prefix of the string. If not provided, defaults
314 to the id of the tests.
315 :return: A bytestring of '<prefix>-<unique_int>'.
319 return '%s-%d' % (prefix, self.getUniqueInteger())
321 def onException(self, exc_info):
322 """Called when an exception propogates from test code.
324 :seealso addOnException:
326 for handler in self.__exception_handlers:
330 def _report_error(self, result, err):
331 self._report_traceback()
332 result.addError(self, details=self.getDetails())
335 def _report_expected_failure(self, result, err):
336 result.addExpectedFailure(self, details=self.getDetails())
339 def _report_failure(self, result, err):
340 self._report_traceback()
341 result.addFailure(self, details=self.getDetails())
344 def _report_skip(self, result, err):
348 reason = "no reason given."
349 self._add_reason(reason)
350 result.addSkip(self, details=self.getDetails())
352 def _report_traceback(self):
353 self.addDetail('traceback',
354 content.TracebackContent(sys.exc_info(), self))
357 def _report_unexpected_success(self, result, err):
358 result.addUnexpectedSuccess(self, details=self.getDetails())
360 def run(self, result=None):
361 return self.__RunTest(self, self.exception_handlers).run(result)
363 def _run_setup(self, result):
364 """Run the setUp function for this test.
366 :param result: A testtools.TestResult to report activity to.
367 :raises ValueError: If the base class setUp is not called, a
368 ValueError is raised.
371 if not self.__setup_called:
373 "TestCase.setUp was not called. Have you upcalled all the "
374 "way up the hierarchy from your setUp? e.g. Call "
375 "super(%s, self).setUp() from your setUp()."
376 % self.__class__.__name__)
378 def _run_teardown(self, result):
379 """Run the tearDown function for this test.
381 :param result: A testtools.TestResult to report activity to.
382 :raises ValueError: If the base class tearDown is not called, a
383 ValueError is raised.
386 if not self.__teardown_called:
388 "TestCase.tearDown was not called. Have you upcalled all the "
389 "way up the hierarchy from your tearDown? e.g. Call "
390 "super(%s, self).tearDown() from your tearDown()."
391 % self.__class__.__name__)
393 def _run_test_method(self, result):
394 """Run the test method for this test.
396 :param result: A testtools.TestResult to report activity to.
399 absent_attr = object()
401 method_name = getattr(self, '_testMethodName', absent_attr)
402 if method_name is absent_attr:
404 method_name = getattr(self, '_TestCase__testMethodName')
405 testMethod = getattr(self, method_name)
409 unittest.TestCase.setUp(self)
410 self.__setup_called = True
413 unittest.TestCase.tearDown(self)
414 self.__teardown_called = True
417 # Python 2.4 did not know how to copy functions.
418 if types.FunctionType not in copy._copy_dispatch:
419 copy._copy_dispatch[types.FunctionType] = copy._copy_immutable
423 def clone_test_with_new_id(test, new_id):
424 """Copy a TestCase, and give the copied test a new id.
426 This is only expected to be used on tests that have been constructed but
429 newTest = copy.copy(test)
430 newTest.id = lambda: new_id
435 """A decorator to skip unit tests.
437 This is just syntactic sugar so users don't have to change any of their
438 unit tests in order to migrate to python 2.7, which provides the
439 @unittest.skip decorator.
441 def decorator(test_item):
442 if wraps is not None:
444 def skip_wrapper(*args, **kwargs):
445 raise TestCase.skipException(reason)
447 def skip_wrapper(test_item):
448 test_item.skip(reason)
453 def skipIf(condition, reason):
454 """Skip a test if the condition is true."""
462 def skipUnless(condition, reason):
463 """Skip a test unless the condition is true."""