how to handle Exceptions in programs


This is a continuation of how to test that an Exception is raised


test_catching_exceptions_w_messages

  • I add a failing test to test_exceptions.py

    def test_catching_exceptions_w_messages(self):
        src.exceptions.raise_exception()
    

    and the terminal shows AttributeError

    AttributeError: module 'src.exceptions' has no attribute 'raise_exception'
    

green: make it pass

  • I add the name to exceptions.py

    def function_name():
        return None
    
    
    raise_exception
    

    and get NameError

    NameError: name 'raise_exception' is not defined
    
  • I point it to None

    raise_exception = None
    

    and the terminal shows TypeError

    TypeError: 'NoneType' object is not callable
    
  • When I make it a function

    def raise_exception():
        return None
    

    the test passes

  • I want the function to raise an Exception when it is called

    def test_catching_exceptions_w_messages(self):
        with self.assertRaises(Exception):
            src.exceptions.raise_exception()
    

    the terminal shows AssertionError

    AssertionError: Exception not raised
    
  • I use the raise statement

    def raise_exception():
        raise Exception
    

    and the test is green

  • I can use the unittest.TestCase.assertRaisesRegex method to be more specific in tests, it checks that the code in its context raises the Exception it is given, with the message it is given, it uses Regular Expressions for this

    def test_catching_exceptions_w_messages(self):
        with self.assertRaisesRegex(
            Exception, 'BOOM!'
        ):
            src.exceptions.raise_exception()
    

    the terminal shows AssertionError

    AssertionError: "BOOM!" does not match ""
    
  • the Exception is right, the message is not. I add it

    def raise_exception():
        raise Exception('BOOM!')
    

    and the test passes. Time to add an how to test that an Exception is raised to the program


test_catching_failure

red: make it fail

I add a new failing test

def test_catching_failure(self):
    self.assertEqual(
        src.exceptions.an_exception_handler(
            src.exceptions.raise_exception
        ),
        'failed'
    )

the terminal shows AttributeError

AttributeError: module 'src.exceptions' has no attribute 'an_exception_handler'

green: make it pass

  • When I add the name

    def raise_exception():
        raise Exception('BOOM')
    
    
    an_exception_handler
    

    I get NameError

    NameError: name 'an_exception_handler' is not defined
    
  • I point it to None

    an_exception_handler = None
    

    and get TypeError

    TypeError: 'NoneType' object is not callable
    
  • then I make it a function

    def an_exception_handler():
        return None
    

    and get a different message for the TypeError

    TypeError: an_exception_handler() takes 0 positional arguments but 1 was given
    
  • I make the function take input

    def an_exception_handler(argument):
        return None
    

    and the terminal shows AssertionError

    AssertionError: None != 'failed'
    

    the result of calling src.exceptions.an_exception_handler is None, the test expects 'failed'

  • I change the return statement to match the expectation

    def an_exception_handler(argument):
        return 'failed'
    

    and the test passes.


test_catching_success

I want an_exception_handler to process its input and return failed when an Exception happens or success when it does not.

red: make it fail

I add a new test

def test_catching_success(self):
    self.assertEqual(
        src.exceptions.an_exception_handler(
            src.exceptions.does_not_raise_exception
        ),
        'succeeded'
    )

and get AttributeError

AttributeError: module 'src.exceptions' has no attribute 'does_not_raise_exception'

green: make it pass

  • I add the name to exceptions.py

    def raise_exception():
        raise Exception('BOOM')
    
    
    does_not_raise_exception
    
    
    def an_exception_handler(argument):
        return 'failed'
    

    and the terminal shows NameError

    NameError: name 'does_not_raise_exception' is not defined
    
  • I point it to None

    does_not_raise_exception = None
    

    and get AssertionError

    AssertionError: 'failed' != 'succeeded'
    

    src.exceptions.an_exception_handler still returns 'failed', the test expects 'succeeded'

  • I make an_exception_handler return its input

    def an_exception_handler(argument):
        return argument
        return 'failed'
    

    and the terminal shows AssertionError

    FAILED tests/test_exceptions.py::TestExceptions::test_catching_failure - AssertionError: <function raise_exception at 0xabcd12e34567> != 'failed'
    FAILED tests/test_exceptions.py::TestExceptions::test_catching_success - AssertionError: None != 'succeeded'
    
    • test_catching_failure fails - in this test an_exception_handler returns the name of the function it receives and its address in memory

    • test_catching_success still fails - in this test an_exception_handler returns does_not_raise_exception which points to None

  • I rename the input parameter to describe it better

    def an_exception_handler(a_function):
        return a_function
        return 'failed'
    

    then make an_exception_handler return the result of a call to its input as a function

    def an_exception_handler(a_function):
        return a_function()
        return 'failed'
    
    • and the terminal shows TypeError

      a_function = None
      
          def an_exception_handler(a_function):
      >       return a_function()
      E       TypeError: 'NoneType' object is not callable
      

      because does_not_raise_exception points to None, which is not callable. I make it a function to fix this

      def does_not_raise_exception():
          return None
      

      and the terminal shows AssertionError

      AssertionError: None != 'succeeded'
      
    • the result of calling src.exceptions.raise_exception in test_catching_failure is an Exception with a message

      FAILED tests/test_exceptions.py::TestExceptions::test_catching_failure - Exception: 'BOOM!'
      

how to use try…except…else

  • when I add a try statement

    def an_exception_handler(a_function):
        try:
            a_function()
        except Exception:
            return 'failed'
    

    test_catching_failure passes and the terminal still shows AssertionError

    AssertionError: None != 'succeeded'
    
  • I add an else clause

    def an_exception_handler(a_function):
        try:
            a_function()
        except Exception:
            return 'failed'
        else:
            return None
    

    then change its return statement

    def an_exception_handler(a_function):
        try:
            a_function()
        except Exception:
            return 'failed'
        else:
            return 'succeeded'
    

    and the terminal shows passing tests.

    The try statement is used to catch/handle exceptions in Python. It allows the program to make a decision when it runs into an Exception. I think of it as

    • try running this

    • except Exception - when running this raises Exception, run the code in this block

    • else - when running this does NOT raise Exception, run the code in this block

    In this case

    • try calling a_function()

    • except Exception - when calling a_function() raises Exception return 'failed'

    • else - when calling a_function() does NOT raise Exception return 'succeeded'

  • I can be more specific with the Exception in the except block, for example

    def an_exception_handler(a_function):
        try:
            a_function()
        except ModuleNotFoundError:
            return 'failed'
        else:
            return 'succeeded'
    

    shows this in the terminal

    Exception: 'BOOM!'
    

    because Exception is not ModuleNotFoundError, the try statement will only catch the Exception given in the except block and its children, all others will be raised

  • I change it back to what works

    def an_exception_handler(a_function):
        try:
            a_function()
        except Exception:
            return 'failed'
        else:
            return 'succeeded'
    

    and the terminal shows green again


review

I ran tests to show how to cause Exceptions, and catch or handle them in tests and programs. Would you like to test measuring sleep duration?


how to handle Exceptions: tests and solutions