how to test that an Exception is raised
When an error happens in Python, an Exception is raised to stop the program, this means nothing past the line that caused the error will run.
It is useful because there is a problem that must be solved for the program to continue. It is a problem when it causes the program to stop early.
What if I want to test that a program raises an Exception? If the Exception is raised, the test will not continue past the line that caused it.
I can use the try statement to test that some code raises an Exception and continue past the line that caused it.
preview
I have these tests by the end of the chapter
1import src.exceptions
2import unittest
3
4
5class TestExceptions(unittest.TestCase):
6
7 @staticmethod
8 def assert_raises(code, exception):
9 try:
10 exec(code)
11 except exception:
12 pass
13 else:
14 raise AssertionError
15
16 def test_catching_module_not_found_error(self):
17 self.assert_raises(
18 'import does_not_exist', ModuleNotFoundError
19 )
20 with self.assertRaises(ModuleNotFoundError):
21 import does_not_exist
23 def test_catching_name_error(self):
24 self.assert_raises('not_defined', NameError)
25 with self.assertRaises(NameError):
26 not_defined
27
28 def test_catching_attribute_error(self):
29 self.assert_raises(
30 'src.exceptions.does_not_exist', AttributeError
31 )
32 with self.assertRaises(AttributeError):
33 src.exceptions.does_not_exist
34
35 def test_catching_type_error(self):
36 self.assert_raises(
37 "src.exceptions.function_name('the input')",
38 TypeError
39 )
40 with self.assertRaises(TypeError):
41 src.exceptions.function_name('the input')
43 def test_catching_index_error(self):
44 self.assert_raises("'a string'[8]", IndexError)
45 self.assert_raises("'a string'[-9]", IndexError)
46
47 with self.assertRaises(IndexError):
48 'a string'[8]
49 with self.assertRaises(IndexError):
50 'a string'[-9]
51
52 self.assert_raises(
53 "(0, 1, 2, 'n')[100]", IndexError
54 )
55 self.assert_raises(
56 "(0, 1, 2, 'n')[-100]", IndexError
57 )
58
59 with self.assertRaises(IndexError):
60 (0, 1, 2, 'n')[100]
61 with self.assertRaises(IndexError):
62 (0, 1, 2, 'n')[-100]
64 def test_catching_key_error(self):
65 self.assert_raises(
66 "{'key': 'value'}['not_in_dictionary']",
67 KeyError
68 )
69 with self.assertRaises(KeyError):
70 {'key': 'value'}['not_in_dictionary']
71
72 def test_catching_zero_division_error(self):
73 self.assert_raises('1 / 0', ZeroDivisionError)
74 with self.assertRaises(ZeroDivisionError):
75 1 / 0
76
77 def test_catching_exceptions(self):
78 self.assert_raises('raise Exception', Exception)
79 with self.assertRaises(Exception):
80 raise Exception
81
82
83# Exceptions seen
84# AssertionError
85# ModuleNotFoundError
86# NameError
87# AttributeError
88# TypeError
89# SyntaxError
90# IndexError
91# KeyError
92# ZeroDivisionError
questions about testing Exceptions
requirements
start the project
I open a terminal
I give
exceptionsas the first argument to themakePythonTddprogram./makePythonTdd.sh exceptions.\makePythonTdd.ps1 exceptionsthe terminal is my friend, and shows AssertionError
======================== FAILURES ========================== ______________ Testexceptions.test_failure _________________ self = <tests.test_exceptions.Testexceptions testMethod=test_failure> def test_failure(self): > self.assertFalse(True) E AssertionError: True is not false tests/test_exceptions.py:7: AssertionError ================== short test summary info ================== FAILED tests/test_exceptions.py::Testexceptions::test_failure - AssertionError: True is not false ===================== 1 failed in X.YZs =====================I hold ctrl (Windows/Linux) or option/command (MacOS) on the keyboard and use the mouse to click on
tests/test_exceptions.py:7to put the cursor on line 7I change assertFalse to assertTrue
4class Testexceptions(unittest.TestCase): 5 6 def test_failure(self): 7 # self.assertFalse(True) 8 self.assertTrue(True) 9 10 11# Exceptions seenthe test passes.
I open a new terminal then change directory to
exceptionscd exceptionsI add the new files and folder to git for tracking
git add .I add a git commit message
git commit -am 'setup project'
test_catching_module_not_found_error
ModuleNotFoundError is raised when I try to import a module that does NOT exist.
RED: make it fail
I go back to the terminal where the tests are running
I change
TestexceptionstoTestExceptionsto match the CapWords format1import unittest 2 3 4class TestExceptions(unittest.TestCase): 5 6 def test_failure(self):I change test_failure to
test_catching_module_not_found_errorwith an import statement intests/test_exceptions.py4class TestExceptions(unittest.TestCase): 5 6 def test_catching_module_not_found_error(self): 7 import does_not_exist 8 9 10# Exceptions seenthe terminal is my friend, and shows ModuleNotFoundError
ModuleNotFoundError: No module named 'does_not_exist'I cannot import a module that does not exist. A module is any file that ends in
.pyI add ModuleNotFoundError to the list of Exceptions seen
10# Exceptions seen 11# AssertionError 12# ModuleNotFoundError
I can make a file named does_not_exist.py to solve the problem. What I want to do is catch/handle the Exception in the test to show that import does_not_exist raises ModuleNotFoundError when the file does NOT exist and the test can continue running after confirming the Exception.
how to handle Exceptions
The try statement is like an if statement for Exceptions. It tells the program what to do if an Exception is raised. A simple way to think of it is
trysomethingexcept- if something raises an Exception do something else
If the statement in the try clause raises the Exception in the except clause, Python runs the code in the except block
try:
┌───┴── do something
└── except Exception:
└── do something else
If the statement in the try clause block works, Python exits the try statement
try:
┌───┴── do something
│ except Exception:
│ do something else
GREEN: make it pass
I add a try statement to test_catching_module_not_found_error to handle ModuleNotFoundError
6 def test_catching_module_not_found_error(self): 7 try: 8 import does_not_exist 9 except ModuleNotFoundError: 10 pass 11 12 13# Exceptions seenthe test passes because
import does_not_existraises ModuleNotFoundError.try: ┌───┴── import does_not_exist └── except ModuleNotFoundError: └── passI add a git commit message
git commit -am \ 'add test_catching_module_not_found_error'
ModuleNotFoundError is raised when I try to import a module that does NOT exist.
test_catching_name_error
NameError is raised when I try to use a name that is not defined in the file I am working in.
RED: make it fail
I add a test for NameError
9 except ModuleNotFoundError: 10 pass 11 12 def test_catching_name_error(self): 13 not_defined 14 15 16# Exceptions seenthe terminal is my friend, and shows NameError
NameError: name 'not_defined' is not definedbecause there is no definition for
not_definedintests/test_exceptions.py.I add NameError to the list of Exceptions seen
16# Exceptions seen 17# AssertionError 18# ModuleNotFoundError 19# NameError
GREEN: make it pass
I add a try statement to test_catching_name_error to handle NameError
12 def test_catching_name_error(self): 13 try: 14 not_defined 15 except NameError: 16 pass 17 18 19# Exceptions seenthe test passes because
not_definedraises NameError.try: ┌───┴── not_defined └── except NameError: └── passI add a git commit message
git commit -am \ 'add test_catching_name_error'
NameError is raised when I try to use a name that is not defined in the file.
test_catching_attribute_error
AttributeError is raised when I try to get something that does NOT exist from an object that exists.
RED: make it fail
I add a test for AttributeError
15 except NameError: 16 pass 17 18 def test_catching_attribute_error(self): 19 src.exceptions.does_not_exist 20 21 22# Exceptions seenthe terminal is my friend, and shows NameError
NameError: name 'src' is not definedI add an import statement at the top of the file for the module
1import src.exceptions 2import unittestthe terminal is my friend, and shows AttributeError
AttributeError: module 'src.exceptions' has no attribute 'does_not_exist'src.exceptions.does_not_existis like an addresssrcis thesrcfoldersrc.exceptions.does_not_existis pointing to something nameddoes_not_existin__init__.pyin theexceptionsfolder in thesrcfolder
the failure happens because Python cannot find
does_not_existin the__init__.pyfile in theexceptionsfolder in thesrcfolder. I tried to get something that does NOT exist from an object that exists.I add AttributeError to the list of Exceptions seen
23# Exceptions seen 24# AssertionError 25# ModuleNotFoundError 26# NameError 27# AttributeError
GREEN: make it pass
I add a try statement to test_catching_attribute_error to handle AttributeError
19 def test_catching_attribute_error(self): 20 try: 21 src.exceptions.does_not_exist 22 except AttributeError: 23 pass 24 25 26# Exceptions seenthe test passes because
src.exceptions.does_not_existraises AttributeErrortry: └── src.exceptions.does_not_exist └── src └── exceptions ┌───────────────┴── __init__.py └── except AttributeError: └── passI add a git commit message
git commit -am \ 'add test_catching_attribute_error'
AttributeError is raised when I try to get something that does NOT exist from an object that exists.
test_catching_type_error
TypeError is raised when I try to use an object in a way that it cannot be used.
RED: make it fail
I add a test for TypeError
22 except AttributeError: 23 pass 24 25 def test_catching_type_error(self): 26 src.exceptions.function_name('the input') 27 28 29# Exceptions seenthe terminal is my friend, and shows AttributeError
AttributeError: module 'src.exceptions' has no attribute 'function_name'I open
__init__.pyfrom theexceptionsfolder in thesrcfolderI delete all the text in the file, then add
function_nametosrc/exceptions/__init__.py1function_namethe terminal is my friend, and shows NameError
NameError: name 'function_name' is not definedbecause there is no definition for
function_nameinsrc/exceptions/__init__.pyI point
function_nameto None to define it1function_name = Nonethe terminal is my friend, and shows TypeError
TypeError: 'NoneType' object is not callablea reminder that I cannot call None like a function.
I add TypeError to the list of Exceptions seen, in
tests/test_exceptions.py29# Exceptions seen 30# AssertionError 31# ModuleNotFoundError 32# NameError 33# AttributeError 34# TypeError
GREEN: make it pass
I add a try statement to test_catching_type_error to handle TypeError
25 def test_catching_type_error(self):
26 try:
27 src.exceptions.function_name('the input')
28 except TypeError:
29 pass
30
31
32# Exceptions seen
the test passes because src.exceptions.function_name('the input') raises TypeError.
try:
└── src.exceptions.function_name('the input')
└── src
└── exceptions
└── __init__.py
┌───────────────────┴── function_name = None
└── except TypeError:
└── pass
REFACTOR: make it better
I make
function_namea function insrc/exceptions/__init__.py1def function_name(): 2 return Nonethe test is still green because TypeError is raised since the call from the test -
src.exceptions.function_name('the input')sends'the input'as input and thefunction_namefunction does not take input (the parentheses are empty).I add a parameter to the definition
1def function_name(parameter_name): 2 return Nonethe test is still green, because the statement no longer gets to the except block
try: └── src.exceptions.function_name('the input') └── src └── exceptions └── __init__.py └── def function_name(parameter_name): └── return None except TypeError: passI need a better try statement.
how to use try…except…else
The try statement has an else clause that I can use to make a program do something if the Exception in the except clause is not raised
I add an else clause to the try statement to raise AssertionError if TypeError is not raised
25 def test_catching_type_error(self): 26 try: 27 src.exceptions.function_name('the input') 28 except TypeError: 29 pass 30 else: 31 raise AssertionError 32 33 34# Exceptions seenthe terminal is my friend, and shows AssertionError
E AssertionErrorbecause TypeError is NOT raised since the function call matches the definition.
try: ┌───┴── src.exceptions.function_name('the input') │ └── src │ └── exceptions │ └── __init__.py │ └── def function_name(parameter_name): │ └── return None │ except TypeError: │ pass └── else: └── raise AssertionErrorI undo the change
1def function_name(): 2 returnthe test is green again.
I add a git commit message
git commit -am \ 'add test_catching_type_error'
TypeError is raised when I try to use an object in a way that it cannot be used.
add else clause to try statements
I add the else clause to test_catching_attribute_error to make sure it raises AssertionError if AttributeError is not raised by the code in the try block
19 def test_catching_attribute_error(self): 20 try: 21 src.exceptions.does_not_exist 22 except AttributeError: 23 pass 24 else: 25 raise AssertionError 26 27 def test_catching_type_error(self):the test is still green.
I add the else clause to test_catching_name_error to make sure it raises AssertionError if NameError is not raised by the code in the try block
13 def test_catching_name_error(self): 14 try: 15 not_defined 16 except NameError: 17 pass 18 else: 19 raise AssertionError 20 21 def test_catching_attribute_error(self):still green.
I add the else clause to test_catching_module_not_found_error to make sure it raises AssertionError if ModuleNotFoundError is not raised by the code in the try block
7 def test_catching_module_not_found_error(self): 8 try: 9 import does_not_exist 10 except ModuleNotFoundError: 11 pass 12 else: 13 raise AssertionError 14 15 def test_catching_name_error(self):green.
I add a git commit message
git commit -am \ 'add else clause to try statements'
extract assert_raises method
The try statements all look the same, the only differences are the code in the try block and the Exception in the except clause
try:
code
except Exception:
pass
else:
raise AssertionError
RED: make it fail
I add a method for the try statement
5class TestExceptions(unittest.TestCase): 6 7 @staticmethod 8 def assert_raises(code, exception): 9 try: 10 code 11 except exception: 12 pass 13 else: 14 raise AssertionError 15 16 def test_catching_module_not_found_error(self):I use the assert_raises method in test_catching_module_not_found_error
16 def test_catching_module_not_found_error(self): 17 self.assert_raises( 18 import does_not_exist, ModuleNotFoundError 19 ) 20 try:the terminal is my friend, and shows SyntaxError
SyntaxError: invalid syntaxbecause
import does_not_existruns before it is passed as input to the assert_raises method. I need a way to pass it as a value that will be run inside the assert_raises method not before.I add SyntaxError to the list of Exceptions seen
52# Exceptions seen 53# AssertionError 54# ModuleNotFoundError 55# NameError 56# AttributeError 57# TypeError 58# SyntaxError
GREEN: make it pass
I change the
import does_not_existline in test_catching_module_not_found_error to a string16 def test_catching_module_not_found_error(self): 17 self.assert_raises( 18 'import does_not_exist', ModuleNotFoundError 19 ) 20 try: 21 import does_not_exist 22 except ModuleNotFoundError: 23 pass 24 else: 25 raise AssertionError 26 27 def test_catching_name_error(self):the terminal is my friend, and shows AssertionError
E AssertionErrorbecause the string (
'import does_not_exist') does not raise ModuleNotFoundErrorassert_raises('import does_not_exist', ModuleNotFoundError) └── def assert_raises(code, exception): ├── code = 'import does_not_exist' ├── exception = ModuleNotFoundError └── try: ┌───┴── code │ except exception: │ pass └── else: └── raise AssertionErrorI still need a way to run the code in the string inside the assert_raises method.
the exec function
I can use the exec built-in function to run any Python code I pass as a string to it.
I add exec to the try block in the assert_raises method
7 @staticmethod 8 def assert_raises(code, exception): 9 try: 10 exec(code) 11 except exception: 12 pass 13 else: 14 raise AssertionError 15 16 def test_catching_module_not_found_error(self):the test passes, showing that
import does_not_existraises ModuleNotFoundError which happens when I try to import a module that does NOT exist.assert_raises('import does_not_exist', ModuleNotFoundError) └── def assert_raises(code, exception): ├── code = 'import does_not_exist' ├── exception = ModuleNotFoundError └── try: ┌───┴── exec(code) └── except exception: └── pass else: raise AssertionError
REFACTOR: make it better
I remove the try block from test_catching_module_not_found_error because it is now a repetition of the assert_raises method
16 def test_catching_module_not_found_error(self): 17 self.assert_raises( 18 'import does_not_exist', ModuleNotFoundError 19 ) 20 21 def test_catching_name_error(self):I use the assert_raises method in test_catching_name_error
21def test_catching_name_error(self): 22 self.assert_raises('not_defined', ModuleNotFoundError) 23 try:the terminal is my friend, and shows NameError
NameError: name 'not_defined' is not definedbecause NameError is not ModuleNotFoundError or a child of ModuleNotFoundError.
I change the expected Exception to NameError
21 def test_catching_name_error(self): 22 self.assert_raises('not_defined', NameError) 23 try: 24 not_defined 25 except NameError: 26 pass 27 else: 28 raise AssertionError 29 30 def test_catching_attribute_error(self):the test passes, showing that
not_definedraises NameError which happens when I try to use a name that is not defined in the file.I remove the try block from test_catching_name_error because it is now a repetition of the assert_raises method
21 def test_catching_name_error(self): 22 self.assert_raises('not_defined', NameError) 23 24 def test_catching_attribute_error(self):I use the assert_raises method in test_catching_attribute_error
24 def test_catching_attribute_error(self): 25 self.assert_raises( 26 'src.exceptions.does_not_exist', NameError 27 ) 28 try:the terminal is my friend, and shows AttributeError
AttributeError: module 'src.exceptions' has no attribute 'does_not_exist'because AttributeError is not NameError or a child of NameError.
I change the expected Exception to AttributeError
24 def test_catching_attribute_error(self): 25 self.assert_raises( 26 'src.exceptions.does_not_exist', AttributeError 27 ) 28 try: 29 src.exceptions.does_not_exist 30 except AttributeError: 31 pass 32 else: 33 raise AssertionError 34 35 def test_catching_type_error(self):the test passes, showing that
src.exceptions.does_not_existraises AttributeError which happens when I try to get something that does NOT exist from an object that exists.I remove the try block from test_catching_attribute_error because it is now a repetition of the assert_raises method
24 def test_catching_attribute_error(self): 25 self.assert_raises( 26 'src.exceptions.does_not_exist', AttributeError 27 ) 28 29 def test_catching_type_error(self):I use the assert_raises method in test_catching_type_error
29 def test_catching_type_error(self): 30 self.assert_raises( 31 "src.exceptions.function_name('the input')", 32 AttributeError 33 ) 34 try:the terminal is my friend, and shows TypeError
TypeError: function_name() takes 0 positional arguments but 1 was givenbecause TypeError is not AttributeError or a child of AttributeError.
I change the expected Exception to TypeError
29 def test_catching_type_error(self): 30 self.assert_raises( 31 "src.exceptions.function_name('the input')", 32 TypeError 33 ) 34 try: 35 src.exceptions.function_name('the input') 36 except TypeError: 37 pass 38 else: 39 raise AssertionError 40 41 42# Exceptions seenthe test passes, showing that
src.exceptions.function_name('the input')raises TypeError which happens when I try to use an object in a way that it cannot be used.I remove the try block from test_catching_type_error because it is now a repetition of the assert_raises method
29 def test_catching_type_error(self): 30 self.assert_raises( 31 "src.exceptions.function_name('the input')", 32 TypeError 33 ) 34 35 36# Exceptions seenI add a git commit message
git commit -am \ 'extract assert_raises method'
test_catching_index_error
IndexError is raised when I try to index a list, tuple or string with a number that is
bigger than or the same as the number of items in the list, tuple or string
smaller than the negative of the number of items in the list, tuple or string
RED: make it fail
I add a test for IndexError with a string
29 def test_catching_type_error(self): 30 self.assert_raises( 31 "src.exceptions.function_name('the input')", 32 TypeError 33 ) 34 35 def test_catching_index_error(self): 36 'a string'[0] 37 38 39# Exceptions seenthe test is still green because the first item in a list, tuple or string has
0as its index (its position in the container).I change the index from
0to735 def test_catching_index_error(self): 36 'a string'[7] 37 38 39# Exceptions seenthe test is still green because the index for the last item is the total number of items minus
1, which is7in this case.If I use a number that is bigger than the index of the last item
35 def test_catching_index_error(self): 36 'a string'[8] 37 38 39# Exceptions seenthe terminal is my friend, and shows IndexError
IndexError: string index out of rangeI cannot use a number that is bigger than the index of the last item in a string or that is greater than or equal to the length of the string.
I add IndexError to the list of Exceptions seen
39# Exceptions seen 40# AssertionError 41# ModuleNotFoundError 42# NameError 43# AttributeError 44# TypeError 45# SyntaxError 46# IndexError
GREEN: make it pass
I use the assert_raises method in test_catching_index_error
35 def test_catching_index_error(self):
36 self.assert_raises("'a string'[8]", IndexError)
37
38
39# Exceptions seen
40
41the test passes.
REFACTOR: make it better
I can also index with negative numbers, the one for the last item is
-1, like reading from right to left35 def test_catching_index_error(self): 36 self.assert_raises("'a string'[8]", IndexError) 37 'a string'[-1] 38 39 40# Exceptions seenthe test is still green.
I change the index from
-1to-8for the first item, which is negative the total number of items (like reading from right to left)35 def test_catching_index_error(self): 36 self.assert_raises("'a string'[8]", IndexError) 37 'a string'[-8] 38 39 40# Exceptions seenstill green.
I use a negative number that is smaller than the negative of the number of characters in the string
35 def test_catching_index_error(self): 36 self.assert_raises("'a string'[8]", IndexError) 37 'a string'[-9] 38 39 40# Exceptions seenthe terminal is my friend, and shows IndexError
IndexError: string index out of rangeI use the assert_raises method to handle the Exception
35 def test_catching_index_error(self): 36 self.assert_raises("'a string'[8]", IndexError) 37 self.assert_raises("'a string'[-9]", IndexError) 38 39 40# Exceptions seenthe test is green again. I cannot use a number that is smaller than the negative of the total number of items in the string to index the string.
I add a tuple to test_catching_index_error
35 def test_catching_index_error(self): 36 self.assert_raises("'a string'[8]", IndexError) 37 self.assert_raises("'a string'[-9]", IndexError) 38 39 (0, 1, 2, 'n')[1] 40 41 42# Exceptions seenthe test is still green because
1is the index of the second item.I use a number that is bigger than the number of items in the tuple
35 def test_catching_index_error(self): 36 self.assert_raises("'a string'[8]", IndexError) 37 self.assert_raises("'a string'[-9]", IndexError) 38 39 (0, 1, 2, 'n')[100] 40 41 42# Exceptions seenthe terminal is my friend, and shows IndexError
IndexError: tuple index out of rangeI use the assert_raises method
35 def test_catching_index_error(self): 36 self.assert_raises("'a string'[8]", IndexError) 37 self.assert_raises("'a string'[-9]", IndexError) 38 39 self.assert_raises( 40 "(0, 1, 2, 'n')[100]", IndexError 41 ) 42 43 44# Exceptions seenthe test passes.
I use a negative number to index the tuple
39 self.assert_raises( 40 "(0, 1, 2, 'n')[100]", IndexError 41 ) 42 (0, 1, 2, 'n')[-2] 43 44 45# Exceptions seenthe test is still green.
I use a number that is smaller than the negative of the number of items in the tuple
39 self.assert_raises( 40 "(0, 1, 2, 'n')[100]", IndexError 41 ) 42 (0, 1, 2, 'n')[-100] 43 44 45# Exceptions seenthe terminal is my friend, and shows IndexError
IndexError: tuple index out of rangeI cannot use a number that is smaller than the negative of the number of items in a tuple.
I use the assert_raises method to catch the Exception
35 def test_catching_index_error(self): 36 self.assert_raises("'a string'[8]", IndexError) 37 self.assert_raises("'a string'[-9]", IndexError) 38 39 self.assert_raises( 40 "(0, 1, 2, 'n')[100]", IndexError 41 ) 42 self.assert_raises( 43 "(0, 1, 2, 'n')[-100]", IndexError 44 ) 45 46 def test_catching_key_error(self):the test passes.
I add a git commit message
git commit -am \ 'add test_catching_index_error'
IndexError is raised when I try to index a list, tuple or string with a number that is
test_catching_key_error
KeyError is raised when I try to use a key that is NOT in a dictionary.
RED: make it fail
I add a test for KeyError with a dictionary
42 self.assert_raises( 43 "(0, 1, 2, 'n')[-100]", IndexError 44 ) 45 46 def test_catching_key_error(self): 47 {'key': 'value'}['key'] 48 49 50# Exceptions seenthe test is green because
'key'is a key of the{'key': 'value'}dictionary.If I use a key that is NOT in the dictionary
46 def test_catching_key_error(self): 47 {'key': 'value'}['not_in_dictionary'] 48 49 50# Exceptions seenthe terminal is my friend, and shows KeyError
KeyError: 'not_in_dictionary'I add KeyError to the list of Exceptions seen
50# Exceptions seen 51# AssertionError 52# ModuleNotFoundError 53# NameError 54# AttributeError 55# TypeError 56# SyntaxError 57# IndexError 58# KeyError
GREEN: make it pass
I use the assert_raises method to catch the Exception in test_catching_key_error
46 def test_catching_key_error(self): 47 self.assert_raises( 48 "{'key': 'value'}['not_in_dictionary']", 49 KeyError 50 ) 51 52 53# Exceptions seenthe test passes.
I add a git commit message
git commit -am \ 'add test_catching_key_error'
KeyError is raised when I try to use a key that is NOT in a dictionary.
test_catching_zero_division_error
ZeroDivisionError is raised when I try to divide a number by 0.
RED: make it fail
I add a test for ZeroDivisionError
46 def test_catching_key_error(self): 47 self.assert_raises( 48 "{'key': 'value'}['not_in_dictionary']", 49 KeyError 50 ) 51 52 def test_catching_zero_division_error(self): 53 1 / 0 54 55 56# Exceptions seenthe terminal is my friend, and shows ZeroDivisionError
ZeroDivisionError: division by zerobecause I cannot divide a number by
0.I add ZeroDivisionError to the list of Exceptions seen
56# Exceptions seen 57# AssertionError 58# ModuleNotFoundError 59# NameError 60# AttributeError 61# TypeError 62# SyntaxError 63# IndexError 64# KeyError 65# ZeroDivisionError
GREEN: make it pass
I use the assert_raises method to catch the Exception in test_catching_zero_division_error
52 def test_catching_zero_division_error(self): 53 self.assert_raises('1 / 0', ZeroDivisionError) 54 55 56# Exceptions seenthe test passes.
I add a git commit message
git commit -am \ 'add test_catching_zero_division_error'
ZeroDivisionError is raised when I try to divide a number by 0.
test_catching_exceptions
how to raise an Exception
I can cause an Exception to happen with the raise statement.
RED: make it fail
I add a test with the raise statement
52 def test_catching_zero_division_error(self): 53 self.assert_raises('1 / 0', ZeroDivisionError) 54 55 def test_catching_exceptions(self): 56 raise Exception 57 58 59# Exceptions seenthe terminal is my friend, and shows Exception
ExceptionException is the mother of all the Exceptions covered so far, they inherit from it.
I can use the raise statement to cause any Exception I want
55 def test_catching_exceptions(self): 56 raise AssertionError 57 58 59# Exceptions seenthe terminal shows the Exception I give the raise statement
AssertionErrorI change the Exception back
55 def test_catching_exceptions(self): 56 raise Exception 57 58 59# Exceptions seenException
GREEN: make it pass
I use the assert_raises method to catch Exception
55 def test_catching_exceptions(self):
56 self.assert_raises('raise Exception', Exception)
57
58
59# Exceptions seen
the test passes.
REFACTOR: make it better
I can use Exception to catch any of the Exceptions that inherit from it (its children/subclasses)
46 def test_catching_key_error(self): 47 self.assert_raises( 48 "{'key': 'value'}['not_in_dictionary']", 49 # KeyError 50 Exception 51 ) 52 53 def test_catching_zero_division_error(self): 54 # self.assert_raises('1 / 0', ZeroDivisionError) 55 self.assert_raises('1 / 0', Exception) 56 57 def test_catching_exceptions(self):the tests are still green.
The problem with using Exception to catch its children, is it does not tell anyone that reads the code what the actual Exception is. It is better to be specific.
From the Zen of Python:
Explicit is better than implicit. I change the Exceptions back.46def test_catching_key_error(self): 47 self.assert_raises( 48 "{'key': 'value'}['not_in_dictionary']", 49 KeyError 50 ) 51 52def test_catching_zero_division_error(self): 53 self.assert_raises('1 / 0', ZeroDivisionError) 54 55def test_catching_exceptions(self):I cannot use sibling or cousin Exceptions to catch other Exceptions
35 def test_catching_index_error(self): 36 self.assert_raises("'a string'[8]", IndexError) 37 self.assert_raises("'a string'[-9]", IndexError) 38 39 self.assert_raises( 40 "(0, 1, 2, 'n')[100]", IndexError 41 ) 42 self.assert_raises( 43 # "(0, 1, 2, 'n')[-100]", IndexError 44 "(0, 1, 2, 'n')[-100]", ModuleNotFoundError 45 ) 46 47 def test_catching_key_error(self):the terminal is my friend, and shows IndexError
IndexError: tuple index out of rangebecause IndexError is not ModuleNotFoundError even though they are both Exceptions.
I undo the change
35 def test_catching_index_error(self): 36 self.assert_raises("'a string'[8]", IndexError) 37 self.assert_raises("'a string'[-9]", IndexError) 38 39 self.assert_raises( 40 "(0, 1, 2, 'n')[100]", IndexError 41 ) 42 self.assert_raises( 43 "(0, 1, 2, 'n')[-100]", IndexError 44 ) 45 46 def test_catching_key_error(self):the test is green again.
I cannot use a child Exceptions to catch its parent Exception.
55 def test_catching_exceptions(self): 56 # self.assert_raises('raise Exception', Exception) 57 self.assert_raises( 58 'raise Exception', ZeroDivisionError 59 ) 60 61 62# Exceptions seenthe terminal is my friend, and shows Exception
Exceptionbecause Exception is not ZeroDivisionError or a child of ZeroDivisionError, even though ZeroDivisionError is an Exception.
I undo the change
55 def test_catching_exceptions(self): 56 self.assert_raises('raise Exception', Exception) 57 58 59# Exceptions seenthe test is green again.
I add a git commit message
git commit -am 'add test_catching_exceptions'
another way to test if an Exception is raised
unittest.TestCase has a method I can use to test if code raises an Exception, it is called assertRaises.
assertRaises checks that the code in its context raises the Exception it is given in parentheses.
I add the failing line for test_catching_exceptions
55 def test_catching_exceptions(self): 56 self.assert_raises('raise Exception', Exception) 57 raise Exception 58 59 60# Exceptions seenI add assertRaises to handle Exception
55 def test_catching_exceptions(self): 56 self.assert_raises('raise Exception', Exception) 57 with self.assertRaises(Exception): 58 raise Exception 59 60 61# Exceptions seenthe test passes, showing that assertRaises checks that the code in its context (
raise Exception), raises the Exception it is given in parentheses.I add a failing line to test_catching_zero_division_error
52 def test_catching_zero_division_error(self): 53 self.assert_raises('1 / 0', ZeroDivisionError) 54 1 / 0 55 56 def test_catching_exceptions(self):the terminal is my friend, and shows ZeroDivisionError
I add assertRaises to handle ZeroDivisionError.
52 def test_catching_zero_division_error(self): 53 self.assert_raises('1 / 0', ZeroDivisionError) 54 with self.assertRaises(ZeroDivisionError): 55 1 / 0 56 57 def test_catching_exceptions(self):the test passes, showing that assertRaises checks that the code in its context (
1 / 0), raises the Exception it is given in parentheses (ZeroDivisionError).I add a failing line to test_catching_key_error
46 def test_catching_key_error(self): 47 self.assert_raises( 48 "{'key': 'value'}['not_in_dictionary']", 49 KeyError 50 ) 51 {'key': 'value'}['not_in_dictionary'] 52 53 def test_catching_zero_division_error(self):I add assertRaises to handle KeyError
46 def test_catching_key_error(self): 47 self.assert_raises( 48 "{'key': 'value'}['not_in_dictionary']", 49 KeyError 50 ) 51 with self.assertRaises(KeyError): 52 {'key': 'value'}['not_in_dictionary'] 53 54 def test_catching_zero_division_error(self):the test passes, showing that assertRaises checks that the code in its context (
{'key': 'value'}['not_in_dictionary']), raises the Exception it is given in parentheses (KeyError).I add a failure with the assertRaises method to test_catching_index_error
35 def test_catching_index_error(self): 36 self.assert_raises("'a string'[8]", IndexError) 37 self.assert_raises("'a string'[-9]", IndexError) 38 39 with self.assertRaises(ZeroDivisionError): 40 'a string'[8] 41 42 self.assert_raises( 43 "(0, 1, 2, 'n')[100]", IndexError 44 )the terminal is my friend, and shows IndexError.
I change ZeroDivisionError to IndexError in test_catching_index_error
35 def test_catching_index_error(self): 36 self.assert_raises("'a string'[8]", IndexError) 37 self.assert_raises("'a string'[-9]", IndexError) 38 39 with self.assertRaises(IndexError): 40 'a string'[8]the test passes, showing that assertRaises checks that the code in its context (
'a string'[8]), raises the Exception it is given in parentheses (IndexError).I add assertRaises for the second statement in test_catching_index_error
39 with self.assertRaises(IndexError): 40 'a string'[8] 41 with self.assertRaises(IndexError): 42 'a string'[0] 43 44 self.assert_raises( 45 "(0, 1, 2, 'n')[100]", IndexError 46 )the terminal is my friend, and shows AssertionError
AssertionError: IndexError not raiseda better error message than the one from my assert_raises method
AssertionErrorI change the statement to make it raise IndexError
39 with self.assertRaises(IndexError): 40 'a string'[8] 41 with self.assertRaises(IndexError): 42 'a string'[-9] 43 44 self.assert_raises( 45 "(0, 1, 2, 'n')[100]", IndexError 46 )the test passes, showing that assertRaises checks that the code in its context (
'a string'[-9]), raises the Exception it is given in parentheses (IndexError).I add assertRaises for the third statement
52 self.assert_raises( 53 "(0, 1, 2, 'n')[100]", IndexError 54 ) 55 self.assert_raises( 56 "(0, 1, 2, 'n')[-100]", IndexError 57 ) 58 59 with self.assertRaises(IndexError): 60 (0, 1, 2, 'n')[100] 61 62 def test_catching_key_error(self):the test is still green.
I add assertRaises for the fourth statement in test_catching_index_error
59 with self.assertRaises(IndexError): 60 (0, 1, 2, 'n')[100] 61 with self.assertRaises(IndexError): 62 (0, 1, 2, 'n')[-100] 63 64 def test_catching_key_error(self):still green.
I use assertRaises in test_catching_type_error
29 def test_catching_type_error(self): 30 self.assert_raises( 31 "src.exceptions.function_name('the input')", 32 TypeError 33 ) 34 with self.assertRaises(TypeError): 35 src.exceptions.function_name('the input') 36 37 def test_catching_index_error(self):green, showing that assertRaises checks that the code in its context (
src.exceptions.function_name('the input')), raises the Exception it is given in parentheses (TypeError).I add assertRaises to test_catching_attribute_error
24 def test_catching_attribute_error(self): 25 self.assert_raises( 26 'src.exceptions.does_not_exist', AttributeError 27 ) 28 with self.assertRaises(AttributeError): 29 src.exceptions.does_not_exist 30 31 def test_catching_type_error(self):still green, showing that assertRaises checks that the code in its context (
src.exceptions.does_not_exist), raises the Exception it is given in parentheses (AttributeError).I use the assertRaises method in test_catching_name_error
21 def test_catching_name_error(self): 22 self.assert_raises('not_defined', NameError) 23 with self.assertRaises(NameError): 24 not_defined 25 26 def test_catching_attribute_error(self):the test is still green.
I use assertRaises in test_catching_module_not_found_error
16 def test_catching_module_not_found_error(self): 17 self.assert_raises( 18 'import does_not_exist', ModuleNotFoundError 19 ) 20 with self.assertRaises(ModuleNotFoundError): 21 import src.exceptions 22 23 def test_catching_name_error(self):the terminal is my friend, and shows AssertionError
AssertionError: ModuleNotFoundError not raisedbecause
import src.exceptionsdoes not raise ModuleNotFoundErrorI change the statement
16 def test_catching_module_not_found_error(self): 17 self.assert_raises( 18 'import does_not_exist', ModuleNotFoundError 19 ) 20 with self.assertRaises(ModuleNotFoundError): 21 import does_not_exist 22 23 def test_catching_name_error(self):the test passes, showing that assertRaises checks that the code in its context (
import does_not_exist), raises the Exception it is given in parentheses (ModuleNotFoundError).I add a git commit message
git commit -am 'use assertRaises'
one exception one exception handler
The assertRaises in test_catching_index_error all catch the same Exception, the only difference is the actual statements that cause IndexError
If I remove the second assertRaises
43 def test_catching_index_error(self): 44 self.assert_raises("'a string'[8]", IndexError) 45 self.assert_raises("'a string'[-9]", IndexError) 46 47 with self.assertRaises(IndexError): 48 'a string'[8] 49 # with self.assertRaises(IndexError): 50 'a string'[-9]the test is still green for
'a string'[-9]which should cause IndexError, this makes it look like a second assertRaises is a repetition.If I add a raise statement before
'a string'[-9]47 with self.assertRaises(IndexError): 48 'a string'[8] 49 # with self.assertRaises(IndexError): 50 raise Exception 51 'a string'[-9]the test is still green, which is not the expected behavior.
Exception is not IndexError and still does not get raised, which means the assertRaises exits after the first line that causes IndexError and does not run the other lines.
It should only catch IndexError NOT Exception since I cannot use a child Exception to catch its parent.
If I move the raise statement above the first IndexError
47 with self.assertRaises(IndexError): 48 raise Exception 49 'a string'[8] 50 # with self.assertRaises(IndexError): 51 'a string'[-9]the terminal is my friend, and shows Exception
Exceptionbecause it is NOT IndexError, this is the expected behavior.
I undo the changes
47 with self.assertRaises(IndexError): 48 'a string'[8] 49 with self.assertRaises(IndexError): 50 'a string'[-9]the test is green again.
As a rule of thumb I write one line of code for one Exception, this way I always know which line caused which Exception.
close the exceptions project
I close
tests/test_exceptions.pyI click in the terminal where the tests are running
I use q on the keyboard to leave the tests. The terminal goes back to the command line.
I change directory to the parent of
exceptionscd ..the terminal is my friend, and shows
...\pumping_pythonI am back in the
pumping_pythondirectory.
review
I ran tests to show that I can use the try statement and assertRaises to catch Exceptions
How many questions can you answer after going through this chapter?
code from the chapter
what is next?
rate pumping python
If this has been a 7 star experience for you, please CLICK HERE to leave a 5 star review of pumping python. It helps other people get into the book too.