1 # Copyright (c) 2008-2010 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.compat import advance_iterator
26 from testtools.matchers import (
30 from testtools.monkey import patch
31 from testtools.runtest import RunTest
32 from testtools.testresult import TestResult
36 # Try to use the python2.7 SkipTest exception for signalling skips.
37 from unittest.case import SkipTest as TestSkipped
39 class TestSkipped(Exception):
40 """Raised within TestCase.run() when a test is skipped."""
44 # Try to use the same exceptions python 2.7 does.
45 from unittest.case import _ExpectedFailure, _UnexpectedSuccess
47 # Oops, not available, make our own.
48 class _UnexpectedSuccess(Exception):
49 """An unexpected success was raised.
51 Note that this exception is private plumbing in testtools' testcase
55 class _ExpectedFailure(Exception):
56 """An expected failure occured.
58 Note that this exception is private plumbing in testtools' testcase
63 class TestCase(unittest.TestCase):
64 """Extensions to the basic TestCase.
66 :ivar exception_handlers: Exceptions to catch from setUp, runTest and
67 tearDown. This list is able to be modified at any time and consists of
68 (exception_class, handler(case, result, exception_value)) pairs.
71 skipException = TestSkipped
73 def __init__(self, *args, **kwargs):
74 """Construct a TestCase.
76 :param testMethod: The name of the method to run.
77 :param runTest: Optional class to use to execute the test. If not
78 supplied testtools.runtest.RunTest is used. The instance to be
79 used is created when run() is invoked, so will be fresh each time.
81 unittest.TestCase.__init__(self, *args, **kwargs)
83 self._unique_id_gen = itertools.count(1)
84 self._traceback_id_gen = itertools.count(0)
85 self.__setup_called = False
86 self.__teardown_called = False
87 # __details is lazy-initialized so that a constructed-but-not-run
88 # TestCase is safe to use with clone_test_with_new_id.
90 self.__RunTest = kwargs.get('runTest', RunTest)
91 self.__exception_handlers = []
92 self.exception_handlers = [
93 (self.skipException, self._report_skip),
94 (self.failureException, self._report_failure),
95 (_ExpectedFailure, self._report_expected_failure),
96 (_UnexpectedSuccess, self._report_unexpected_success),
97 (Exception, self._report_error),
99 if sys.version_info < (2, 6):
100 # Catch old-style string exceptions with None as the instance
101 self.exception_handlers.append((type(None), self._report_error))
103 def __eq__(self, other):
104 eq = getattr(unittest.TestCase, '__eq__', None)
105 if eq is not None and not unittest.TestCase.__eq__(self, other):
107 return self.__dict__ == other.__dict__
110 # We add id to the repr because it makes testing testtools easier.
111 return "<%s id=0x%0x>" % (self.id(), id(self))
113 def addDetail(self, name, content_object):
114 """Add a detail to be reported with this test's outcome.
116 For more details see pydoc testtools.TestResult.
118 :param name: The name to give this detail.
119 :param content_object: The content object for this detail. See
120 testtools.content for more detail.
122 if self.__details is None:
124 self.__details[name] = content_object
126 def getDetails(self):
127 """Get the details dict that will be reported with this test's outcome.
129 For more details see pydoc testtools.TestResult.
131 if self.__details is None:
133 return self.__details
135 def patch(self, obj, attribute, value):
136 """Monkey-patch 'obj.attribute' to 'value' while the test is running.
138 If 'obj' has no attribute, then the monkey-patch will still go ahead,
139 and the attribute will be deleted instead of restored to its original
142 :param obj: The object to patch. Can be anything.
143 :param attribute: The attribute on 'obj' to patch.
144 :param value: The value to set 'obj.attribute' to.
146 self.addCleanup(patch(obj, attribute, value))
148 def shortDescription(self):
151 def skipTest(self, reason):
152 """Cause this test to be skipped.
154 This raises self.skipException(reason). skipException is raised
155 to permit a skip to be triggered at any point (during setUp or the
156 testMethod itself). The run() method catches skipException and
157 translates that into a call to the result objects addSkip method.
159 :param reason: The reason why the test is being skipped. This must
160 support being cast into a unicode string for reporting.
162 raise self.skipException(reason)
164 # skipTest is how python2.7 spells this. Sometime in the future
165 # This should be given a deprecation decorator - RBC 20100611.
168 def _formatTypes(self, classOrIterable):
169 """Format a class or a bunch of classes for display in an error."""
170 className = getattr(classOrIterable, '__name__', None)
171 if className is None:
172 className = ', '.join(klass.__name__ for klass in classOrIterable)
175 def _runCleanups(self, result):
176 """Run the cleanups that have been added with addCleanup.
178 See the docstring for addCleanup for more information.
180 :return: None if all cleanups ran without error, the most recently
181 raised exception from the cleanups otherwise.
183 last_exception = None
184 while self._cleanups:
185 function, arguments, keywordArguments = self._cleanups.pop()
187 function(*arguments, **keywordArguments)
188 except KeyboardInterrupt:
191 exc_info = sys.exc_info()
192 self._report_traceback(exc_info)
193 last_exception = exc_info[1]
194 return last_exception
196 def addCleanup(self, function, *arguments, **keywordArguments):
197 """Add a cleanup function to be called after tearDown.
199 Functions added with addCleanup will be called in reverse order of
200 adding after tearDown, or after setUp if setUp raises an exception.
202 If a function added with addCleanup raises an exception, the error
203 will be recorded as a test error, and the next cleanup will then be
206 Cleanup functions are always called before a test finishes running,
207 even if setUp is aborted by an exception.
209 self._cleanups.append((function, arguments, keywordArguments))
211 def addOnException(self, handler):
212 """Add a handler to be called when an exception occurs in test code.
214 This handler cannot affect what result methods are called, and is
215 called before any outcome is called on the result object. An example
216 use for it is to add some diagnostic state to the test details dict
217 which is expensive to calculate and not interesting for reporting in
220 Handlers are called before the outcome (such as addFailure) that
221 the exception has caused.
223 Handlers are called in first-added, first-called order, and if they
224 raise an exception, that will propogate out of the test running
225 machinery, halting test processing. As a result, do not call code that
226 may unreasonably fail.
228 self.__exception_handlers.append(handler)
230 def _add_reason(self, reason):
231 self.addDetail('reason', content.Content(
232 content.ContentType('text', 'plain'),
233 lambda: [reason.encode('utf8')]))
235 def assertEqual(self, expected, observed, message=''):
236 """Assert that 'expected' is equal to 'observed'.
238 :param expected: The expected value.
239 :param observed: The observed value.
240 :param message: An optional message to include in the error.
242 matcher = Equals(expected)
244 matcher = Annotate(message, matcher)
245 self.assertThat(observed, matcher)
247 failUnlessEqual = assertEquals = assertEqual
249 def assertIn(self, needle, haystack):
250 """Assert that needle is in haystack."""
252 needle in haystack, '%r not in %r' % (needle, haystack))
254 def assertIs(self, expected, observed, message=''):
255 """Assert that 'expected' is 'observed'.
257 :param expected: The expected value.
258 :param observed: The observed value.
259 :param message: An optional message describing the error.
262 message = ': ' + message
264 expected is observed,
265 '%r is not %r%s' % (expected, observed, message))
267 def assertIsNot(self, expected, observed, message=''):
268 """Assert that 'expected' is not 'observed'."""
270 message = ': ' + message
272 expected is not observed,
273 '%r is %r%s' % (expected, observed, message))
275 def assertNotIn(self, needle, haystack):
276 """Assert that needle is not in haystack."""
278 needle not in haystack, '%r in %r' % (needle, haystack))
280 def assertIsInstance(self, obj, klass):
282 isinstance(obj, klass),
283 '%r is not an instance of %s' % (obj, self._formatTypes(klass)))
285 def assertRaises(self, excClass, callableObj, *args, **kwargs):
286 """Fail unless an exception of class excClass is thrown
287 by callableObj when invoked with arguments args and keyword
288 arguments kwargs. If a different type of exception is
289 thrown, it will not be caught, and the test case will be
290 deemed to have suffered an error, exactly as for an
291 unexpected exception.
294 ret = callableObj(*args, **kwargs)
296 return sys.exc_info()[1]
298 excName = self._formatTypes(excClass)
299 self.fail("%s not raised, %r returned instead." % (excName, ret))
300 failUnlessRaises = assertRaises
302 def assertThat(self, matchee, matcher):
303 """Assert that matchee is matched by matcher.
305 :param matchee: An object to match with matcher.
306 :param matcher: An object meeting the testtools.Matcher protocol.
307 :raises self.failureException: When matcher does not match thing.
309 mismatch = matcher.match(matchee)
312 existing_details = self.getDetails()
313 for (name, content) in mismatch.get_details().items():
316 while full_name in existing_details:
317 full_name = "%s-%d" % (name, suffix)
319 self.addDetail(full_name, content)
320 self.fail('Match failed. Matchee: "%s"\nMatcher: %s\nDifference: %s\n'
321 % (matchee, matcher, mismatch.describe()))
323 def defaultTestResult(self):
326 def expectFailure(self, reason, predicate, *args, **kwargs):
327 """Check that a test fails in a particular way.
329 If the test fails in the expected way, a KnownFailure is caused. If it
330 succeeds an UnexpectedSuccess is caused.
332 The expected use of expectFailure is as a barrier at the point in a
333 test where the test would fail. For example:
334 >>> def test_foo(self):
335 >>> self.expectFailure("1 should be 0", self.assertNotEqual, 1, 0)
336 >>> self.assertEqual(1, 0)
338 If in the future 1 were to equal 0, the expectFailure call can simply
339 be removed. This separation preserves the original intent of the test
340 while it is in the expectFailure mode.
342 self._add_reason(reason)
344 predicate(*args, **kwargs)
345 except self.failureException:
346 exc_info = sys.exc_info()
347 self._report_traceback(exc_info)
348 raise _ExpectedFailure(exc_info)
350 raise _UnexpectedSuccess(reason)
352 def getUniqueInteger(self):
353 """Get an integer unique to this test.
355 Returns an integer that is guaranteed to be unique to this instance.
356 Use this when you need an arbitrary integer in your test, or as a
357 helper for custom anonymous factory methods.
359 return advance_iterator(self._unique_id_gen)
361 def getUniqueString(self, prefix=None):
362 """Get a string unique to this test.
364 Returns a string that is guaranteed to be unique to this instance. Use
365 this when you need an arbitrary string in your test, or as a helper
366 for custom anonymous factory methods.
368 :param prefix: The prefix of the string. If not provided, defaults
369 to the id of the tests.
370 :return: A bytestring of '<prefix>-<unique_int>'.
374 return '%s-%d' % (prefix, self.getUniqueInteger())
376 def onException(self, exc_info):
377 """Called when an exception propogates from test code.
379 :seealso addOnException:
381 if exc_info[0] not in [
382 TestSkipped, _UnexpectedSuccess, _ExpectedFailure]:
383 self._report_traceback(exc_info)
384 for handler in self.__exception_handlers:
388 def _report_error(self, result, err):
389 result.addError(self, details=self.getDetails())
392 def _report_expected_failure(self, result, err):
393 result.addExpectedFailure(self, details=self.getDetails())
396 def _report_failure(self, result, err):
397 result.addFailure(self, details=self.getDetails())
400 def _report_skip(self, result, err):
404 reason = "no reason given."
405 self._add_reason(reason)
406 result.addSkip(self, details=self.getDetails())
408 def _report_traceback(self, exc_info):
409 tb_id = advance_iterator(self._traceback_id_gen)
411 tb_label = 'traceback-%d' % tb_id
413 tb_label = 'traceback'
414 self.addDetail(tb_label, content.TracebackContent(exc_info, self))
417 def _report_unexpected_success(self, result, err):
418 result.addUnexpectedSuccess(self, details=self.getDetails())
420 def run(self, result=None):
421 return self.__RunTest(self, self.exception_handlers).run(result)
423 def _run_setup(self, result):
424 """Run the setUp function for this test.
426 :param result: A testtools.TestResult to report activity to.
427 :raises ValueError: If the base class setUp is not called, a
428 ValueError is raised.
431 if not self.__setup_called:
433 "TestCase.setUp was not called. Have you upcalled all the "
434 "way up the hierarchy from your setUp? e.g. Call "
435 "super(%s, self).setUp() from your setUp()."
436 % self.__class__.__name__)
438 def _run_teardown(self, result):
439 """Run the tearDown function for this test.
441 :param result: A testtools.TestResult to report activity to.
442 :raises ValueError: If the base class tearDown is not called, a
443 ValueError is raised.
446 if not self.__teardown_called:
448 "TestCase.tearDown was not called. Have you upcalled all the "
449 "way up the hierarchy from your tearDown? e.g. Call "
450 "super(%s, self).tearDown() from your tearDown()."
451 % self.__class__.__name__)
453 def _run_test_method(self, result):
454 """Run the test method for this test.
456 :param result: A testtools.TestResult to report activity to.
459 absent_attr = object()
461 method_name = getattr(self, '_testMethodName', absent_attr)
462 if method_name is absent_attr:
464 method_name = getattr(self, '_TestCase__testMethodName')
465 testMethod = getattr(self, method_name)
469 unittest.TestCase.setUp(self)
470 self.__setup_called = True
473 unittest.TestCase.tearDown(self)
474 self.__teardown_called = True
477 class PlaceHolder(object):
478 """A placeholder test.
480 `PlaceHolder` implements much of the same interface as `TestCase` and is
481 particularly suitable for being added to `TestResult`s.
484 def __init__(self, test_id, short_description=None):
485 """Construct a `PlaceHolder`.
487 :param test_id: The id of the placeholder test.
488 :param short_description: The short description of the place holder
489 test. If not provided, the id will be used instead.
491 self._test_id = test_id
492 self._short_description = short_description
494 def __call__(self, result=None):
495 return self.run(result=result)
498 internal = [self._test_id]
499 if self._short_description is not None:
500 internal.append(self._short_description)
501 return "<%s.%s(%s)>" % (
502 self.__class__.__module__,
503 self.__class__.__name__,
504 ", ".join(map(repr, internal)))
509 def countTestCases(self):
518 def run(self, result=None):
520 result = TestResult()
521 result.startTest(self)
522 result.addSuccess(self)
523 result.stopTest(self)
525 def shortDescription(self):
526 if self._short_description is None:
529 return self._short_description
532 class ErrorHolder(PlaceHolder):
533 """A placeholder test that will error out when run."""
535 failureException = None
537 def __init__(self, test_id, error, short_description=None):
538 """Construct an `ErrorHolder`.
540 :param test_id: The id of the test.
541 :param error: The exc info tuple that will be used as the test's error.
542 :param short_description: An optional short description of the test.
544 super(ErrorHolder, self).__init__(
545 test_id, short_description=short_description)
549 internal = [self._test_id, self._error]
550 if self._short_description is not None:
551 internal.append(self._short_description)
552 return "<%s.%s(%s)>" % (
553 self.__class__.__module__,
554 self.__class__.__name__,
555 ", ".join(map(repr, internal)))
557 def run(self, result=None):
559 result = TestResult()
560 result.startTest(self)
561 result.addError(self, self._error)
562 result.stopTest(self)
565 # Python 2.4 did not know how to copy functions.
566 if types.FunctionType not in copy._copy_dispatch:
567 copy._copy_dispatch[types.FunctionType] = copy._copy_immutable
570 def clone_test_with_new_id(test, new_id):
571 """Copy a TestCase, and give the copied test a new id.
573 This is only expected to be used on tests that have been constructed but
576 newTest = copy.copy(test)
577 newTest.id = lambda: new_id
582 """A decorator to skip unit tests.
584 This is just syntactic sugar so users don't have to change any of their
585 unit tests in order to migrate to python 2.7, which provides the
586 @unittest.skip decorator.
588 def decorator(test_item):
589 if wraps is not None:
591 def skip_wrapper(*args, **kwargs):
592 raise TestCase.skipException(reason)
594 def skip_wrapper(test_item):
595 test_item.skip(reason)
600 def skipIf(condition, reason):
601 """Skip a test if the condition is true."""
609 def skipUnless(condition, reason):
610 """Skip a test unless the condition is true."""