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¶
test_catching_exceptions_w_messages¶
I add a failing test to
test_exceptions.py38 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.py1def function_name(): 2 return None 3 4 5raise_exception
NameError: name 'raise_exception' is not defined
I point it to None
5raise_exception = NoneTypeError: 'NoneType' object is not callable
when I make
raise_exceptiona function5def 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.py42 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_exceptionfunction inexceptions.py5def raise_exception(): 6 raise Exception
the test passes
I can be more specific when testing for an Exception, I add assertRaisesRegex in
test_exceptions.py42 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.py5def 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.py5def raise_exception(): 6 raise Exception('BOOM!') 7 8 9an_exception_handler
NameError: name 'an_exception_handler' is not defined
I point it to None
9an_exception_handler = NoneTypeError: 'NoneType' object is not callable
I make it a function
9def an_exception_handler(): 10 return None
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_handleris 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.py5def raise_exception(): 6 raise Exception('BOOM!') 7 8 9does_not_raise_exception 10 11 12def an_exception_handler(the_input): 13 return 'failed'
NameError: name 'does_not_raise_exception' is not defined
I point it to None
9does_not_raise_exception = Nonethe terminal shows AssertionError
AssertionError: 'failed' != 'succeeded'
src.exceptions.an_exception_handlerstill returns'failed', the test expects'succeeded'I make
an_exception_handlerreturn its input9does_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'
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_handlerreturn the result of a call to its input as a function12def an_exception_handler(a_function): 13 return a_function() 14 return 'failed'
-
a_function = None def an_exception_handler(a_function): > return a_function() E TypeError: 'NoneType' object is not callable
because
does_not_raise_exceptionpoints to None, which is not callable. I make it a function to make it callable9def 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_exceptionintest_catching_failureis an Exception with a messageException: 'BOOM!'
-
how to use try…except…else¶
I add a try statement to
exceptions.py13def an_exception_handler(a_function): 14 try: 15 a_function() 16 except Exception: 17 return 'failed'
test_catching_failurepasses, the terminal still shows AssertionError fortest_catching_successAssertionError: 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 Exception13def 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
tryrunning thisexcept Exception- when running this raisesException, run the code in this blockelse- when running this does NOT raiseException, run the code in this block
In this case
trycallinga_function()except Exception- when callinga_function()raisesExceptionreturn'failed'else- when callinga_function()does NOT raiseExceptionreturn'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
exceptblock, for example13def 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_failuresException: BOOM!because Exception is not ModuleNotFoundError. The try statement only catches the Exception given in the
exceptblock and its children, all others are raisedI 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