how to handle Exceptions (Errors) in programs

preview

Here are the tests I have by the end of the chapter

 1import src.exceptions
 2import unittest
 3
 4
 5class TestExceptions(unittest.TestCase):
 6
 7    def test_catching_module_not_found_error_in_tests(self):
 8        with self.assertRaises(ModuleNotFoundError):
 9            import does_not_exist
10
11    def test_catching_name_error_in_tests(self):
12        with self.assertRaises(NameError):
13            does_not_exist
14
15    def test_catching_attribute_error_in_tests(self):
16        with self.assertRaises(AttributeError):
17            src.exceptions.does_not_exist
18
19    def test_catching_type_error_in_tests(self):
20        with self.assertRaises(TypeError):
21            src.exceptions.function_name('the_input')
22
23    def test_catching_index_error_in_tests(self):
24        a_list = [1, 2, 3, 'n']
25        with self.assertRaises(IndexError):
26            a_list[4]
27        with self.assertRaises(IndexError):
28            a_list[-5]
29
30    def test_catching_key_error_in_tests(self):
31        with self.assertRaises(KeyError):
32            {'key': 'value'}['does_not_exist']
33
34    def test_catching_zero_division_error_in_tests(self):
35        with self.assertRaises(ZeroDivisionError):
36            1 / 0
37
38    def test_catching_exceptions_in_tests(self):
39        with self.assertRaises(Exception):
40            raise Exception
41
42    def test_catching_exceptions_w_messages(self):
43        with self.assertRaisesRegex(
44            Exception, 'BOOM!'
45        ):
46            src.exceptions.raise_exception()
47
48    def test_catching_failure(self):
49        self.assertEqual(
50            src.exceptions.an_exception_handler(
51                src.exceptions.raise_exception
52            ),
53            'failed'
54        )
55
56    def test_catching_success(self):
57        self.assertEqual(
58            src.exceptions.an_exception_handler(
59                src.exceptions.does_not_raise_exception
60            ),
61            'succeeded'
62        )
63
64
65# Exceptions Encountered
66# AssertionError
67# ModuleNotFoundError
68# NameError
69# AttributeError
70# TypeError
71# IndexError
72# KeyError
73# ZeroDivisionError

requirements

how to test that an Exception is raised


test_catching_exceptions_w_messages

  • I add a failing test to test_exceptions.py

    38    def test_catching_exceptions_in_tests(self):
    39        with self.assertRaises(Exception):
    40            raise Exception
    41
    42    def test_catching_exceptions_w_messages(self):
    43        src.exceptions.raise_exception()
    

    the terminal shows AttributeError

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

GREEN: make it pass

  • I add the name to exceptions.py

    1def function_name():
    2    return None
    3
    4
    5raise_exception
    

    the terminal shows NameError

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

    5raise_exception = None
    

    the terminal shows TypeError

    TypeError: 'NoneType' object is not callable
    
  • when I make raise_exception a function

    5def raise_exception():
    6    return None
    

    the test passes

  • I want the function to raise an Exception when it is called, I add assertRaises to the test in test_exceptions.py

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

    the terminal shows AssertionError

    AssertionError: Exception not raised
    
  • I add a raise statement to the raise_exception function in exceptions.py

    5def raise_exception():
    6    raise Exception
    

    the test passes

  • I can be more specific when testing for an Exception, I add assertRaisesRegex in test_exceptions.py

    42    def test_catching_exceptions_w_messages(self):
    43        with self.assertRaisesRegex(
    44            Exception, 'BOOM!'
    45        ):
    46            src.exceptions.raise_exception()
    

    the terminal shows AssertionError

    AssertionError: "BOOM!" does not match ""
    

    the assertRaisesRegex method checks that the code in its context raises the Exception it is given, with the message it is given. The default message of the Exception is the empty string ('') and the test expects "BOOM!"

  • the Exception is right, the message is not, I add the expected message in exceptions.py

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

    the test passes. Time to add an Exception to the program


test_catching_failure

RED: make it fail

I add a new failing test in test_exceptions.py

42      def test_catching_exceptions_w_messages(self):
43          with self.assertRaisesRegex(
44              Exception, 'BOOM!'
45          ):
46              src.exceptions.raise_exception()
47
48      def test_catching_failure(self):
49          self.assertEqual(
50              src.exceptions.an_exception_handler(
51                  src.exceptions.raise_exception
52              ),
53              'failed'
54          )

the terminal shows AttributeError

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

GREEN: make it pass

  • I add the name to exceptions.py

    5def raise_exception():
    6    raise Exception('BOOM!')
    7
    8
    9an_exception_handler
    

    the terminal shows NameError

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

    9an_exception_handler = None
    

    the terminal shows TypeError

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

     9def an_exception_handler():
    10    return None
    

    the terminal shows TypeError

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

     9def an_exception_handler(the_input):
    10    return None
    

    the terminal shows AssertionError

    AssertionError: None != 'failed'
    

    the result of the call to src.exceptions.an_exception_handler is None and the test expects 'failed'

  • I change the return statement to match the expectation

     9def an_exception_handler(the_input):
    10    return 'failed'
    

    the test passes.


test_catching_success

I want an_exception_handler to process its input and return failed when an Exception happens or return success when an Exception is NOT raised.

RED: make it fail

I add a new test to test_exceptions.py

48      def test_catching_failure(self):
49          self.assertEqual(
50              src.exceptions.an_exception_handler(
51                  src.exceptions.raise_exception
52              ),
53              'failed'
54          )
55
56      def test_catching_success(self):
57          self.assertEqual(
58              src.exceptions.an_exception_handler(
59                  src.exceptions.does_not_raise_exception
60              ),
61              'succeeded'
62          )

the terminal shows AttributeError

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

GREEN: make it pass

  • I add the name to exceptions.py

     5def raise_exception():
     6    raise Exception('BOOM!')
     7
     8
     9does_not_raise_exception
    10
    11
    12def an_exception_handler(the_input):
    13    return 'failed'
    

    the terminal shows NameError

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

    9does_not_raise_exception = None
    

    the terminal shows AssertionError

    AssertionError: 'failed' != 'succeeded'
    

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

  • I make an_exception_handler return its input

     9does_not_raise_exception = None
    10
    11
    12def an_exception_handler(the_input):
    13    return the_input
    14    return 'failed'
    

    the terminal shows AssertionError

    ...::test_catching_failure - AssertionError: <function raise_exception at 0xabcd12e34567> != 'failed'
    ...::TestExceptions::test_catching_success - AssertionError: None != 'succeeded'
    
    • test_catching_failure fails because an_exception_handler returns the name (raise_exception) and address in the computer(0xabcd12e34567) of the function it gets

    • test_catching_success fails because an_exception_handler returns does_not_raise_exception which points to None

  • I change the name of the input parameter to be more descriptive

    12def an_exception_handler(a_function):
    13    return a_function
    14    return 'failed'
    

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

    12def an_exception_handler(a_function):
    13    return a_function()
    14    return 'failed'
    
    • 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 make it callable

       9def does_not_raise_exception():
      10    return None
      11
      12
      13def an_exception_handler(a_function):
      14    return a_function()
      15    return 'failed'
      

      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

      Exception: 'BOOM!'
      

how to use try…except…else

  • I add a try statement to exceptions.py

    13def an_exception_handler(a_function):
    14    try:
    15        a_function()
    16    except Exception:
    17        return 'failed'
    

    test_catching_failure passes, the terminal still shows AssertionError for test_catching_success

    AssertionError: None != 'succeeded'
    

    the try statement is used to handle Exceptions in programs

  • I add an else clause for when a_function() runs without raising an Exception

    13def an_exception_handler(a_function):
    14    try:
    15        a_function()
    16    except Exception:
    17        return 'failed'
    18    else:
    19        return None
    

    the terminal shows AssertionError

    AssertionError: None != 'succeeded'
    

    I change the return statement in the else clause

    13def an_exception_handler(a_function):
    14    try:
    15        a_function()
    16    except Exception:
    17        return 'failed'
    18    else:
    19        return 'succeeded'
    

    the test passes.

    The try statement is used to catch/handle exceptions in Python. It allows the program to choose what it does 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'

    the try statement is how I think of Test Driven Development or the scientific method

    • Try something

    • if it fails, try something else

    • do this as many times as you can until you get what you want

    or in the words of a famous singer

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

    13def an_exception_handler(a_function):
    14    try:
    15        a_function()
    16    except ModuleNotFoundError:
    17        return 'failed'
    18    else:
    19        return 'succeeded'
    

    the terminal shows Exception for test_catching_failures

    Exception: BOOM!
    

    because Exception is not ModuleNotFoundError. The try statement only catches the Exception given in the except block and its children, all others are raised

  • I change it back to what works

    13def an_exception_handler(a_function):
    14    try:
    15        a_function()
    16    except Exception:
    17        return 'failed'
    18    else:
    19        return 'succeeded'
    

    the terminal shows green again! I know how to test that an Exception is raised and how to handle Exceptions (Errors) in programs. I am a master!!


review

I ran tests to show how to cause any Exceptions, and catch or handle them in tests and programs.

Would you like to test TypeError?


Click Here to see the code from this chapter