From 697ca3d0cbd75aac21fe823fe379cf9a7edace4a Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Sun, 28 Dec 2008 14:09:36 +0000 Subject: Issue #4444: Allow assertRaises() to be used as a context handler. --- Doc/library/unittest.rst | 11 +++++++++-- Lib/test/test_unittest.py | 37 +++++++++++++++++++++++++++++++++++++ Lib/unittest.py | 38 ++++++++++++++++++++++++++++++-------- Misc/NEWS | 3 +++ 4 files changed, 79 insertions(+), 10 deletions(-) diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index 792a98e..96eebf2 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -613,8 +613,8 @@ failures. equal, the test will fail with the explanation given by *msg*, or :const:`None`. -.. method:: TestCase.assertRaises(exception, callable, ...) - TestCase.failUnlessRaises(exception, callable, ...) +.. method:: TestCase.assertRaises(exception[, callable, ...]) + TestCase.failUnlessRaises(exception[, callable, ...]) Test that an exception is raised when *callable* is called with any positional or keyword arguments that are also passed to :meth:`assertRaises`. The test @@ -622,6 +622,13 @@ failures. fails if no exception is raised. To catch any of a group of exceptions, a tuple containing the exception classes may be passed as *exception*. + .. versionchanged:: 2.2 + + If *callable* is omitted or None, returns a context manager so that the code + under test can be written inline rather than as a function:: + + with self.failUnlessRaises(some_error_class): + do_something() .. method:: TestCase.failIf(expr[, msg]) TestCase.assertFalse(expr[, msg]) diff --git a/Lib/test/test_unittest.py b/Lib/test/test_unittest.py index 5a82780..df656e4 100644 --- a/Lib/test/test_unittest.py +++ b/Lib/test/test_unittest.py @@ -2284,6 +2284,43 @@ class Test_Assertions(TestCase): self.assertRaises(AssertionError, self.failIfAlmostEqual, 0, .1+.1j, places=0) + def test_assertRaises(self): + def _raise(e): + raise e + self.assertRaises(KeyError, _raise, KeyError) + self.assertRaises(KeyError, _raise, KeyError("key")) + try: + self.assertRaises(KeyError, lambda: None) + except AssertionError as e: + self.assert_("KeyError not raised" in e, str(e)) + else: + self.fail("assertRaises() didn't fail") + try: + self.assertRaises(KeyError, _raise, ValueError) + except ValueError: + pass + else: + self.fail("assertRaises() didn't let exception pass through") + with self.assertRaises(KeyError): + raise KeyError + with self.assertRaises(KeyError): + raise KeyError("key") + try: + with self.assertRaises(KeyError): + pass + except AssertionError as e: + self.assert_("KeyError not raised" in e, str(e)) + else: + self.fail("assertRaises() didn't fail") + try: + with self.assertRaises(KeyError): + raise ValueError + except ValueError: + pass + else: + self.fail("assertRaises() didn't let exception pass through") + + ###################################################################### ## Main ###################################################################### diff --git a/Lib/unittest.py b/Lib/unittest.py index 09c6ca9..4538e30 100644 --- a/Lib/unittest.py +++ b/Lib/unittest.py @@ -174,6 +174,25 @@ class TestResult: (_strclass(self.__class__), self.testsRun, len(self.errors), len(self.failures)) +class AssertRaisesContext: + def __init__(self, expected, test_case): + self.expected = expected + self.failureException = test_case.failureException + def __enter__(self): + pass + def __exit__(self, exc_type, exc_value, traceback): + if exc_type is None: + try: + exc_name = self.expected.__name__ + except AttributeError: + exc_name = str(self.expected) + raise self.failureException( + "{0} not raised".format(exc_name)) + if issubclass(exc_type, self.expected): + return True + # Let unexpected exceptions skip through + return False + class TestCase: """A class whose instances are single test cases. @@ -324,22 +343,25 @@ class TestCase: """Fail the test unless the expression is true.""" if not expr: raise self.failureException, msg - def failUnlessRaises(self, excClass, callableObj, *args, **kwargs): + def failUnlessRaises(self, excClass, callableObj=None, *args, **kwargs): """Fail unless an exception of class excClass is thrown by callableObj when invoked with arguments args and keyword arguments kwargs. If a different type of exception is thrown, it will not be caught, and the test case will be deemed to have suffered an error, exactly as for an unexpected exception. + + If called with callableObj omitted or None, will return a + context object used like this:: + + with self.failUnlessRaises(some_error_class): + do_something() """ - try: + context = AssertRaisesContext(excClass, self) + if callableObj is None: + return context + with context: callableObj(*args, **kwargs) - except excClass: - return - else: - if hasattr(excClass,'__name__'): excName = excClass.__name__ - else: excName = str(excClass) - raise self.failureException, "%s not raised" % excName def failUnlessEqual(self, first, second, msg=None): """Fail if the two objects are unequal as determined by the '==' diff --git a/Misc/NEWS b/Misc/NEWS index e60e02a..d42711d 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -91,6 +91,9 @@ Core and Builtins Library ------- +- Issue #4444: Allow assertRaises() to be used as a context handler, so that + the code under test can be written inline if more practical. + - Issue #4739: Add pydoc help topics for symbols, so that e.g. help('@') works as expected in the interactive environment. -- cgit v0.12