how to make a calculator part 4
I want to practice using lists with the calculator project
open the project
I change directory to the
calculatorfoldercd calculatorthe terminal shows I am in the
calculatorfolder.../pumping_python/calculatorI activate the virtual environment
source .venv/bin/activateAttention
on Windows without Windows Subsystem for Linux use
.venv/bin/activate.ps1NOTsource .venv/bin/activate.venv/scripts/activate.ps1the terminal shows
(.venv) .../pumping_python/calculatorI use
pytest-watchto run the testspytest-watchthe terminal shows
rootdir: .../pumping_python/calculator collected 5 items tests/test_calculator.py .... [100%] ============================ 5 passed in X.YZs =============================I hold ctrl on the keyboard and click on
tests/test_calculator.pyto open it in the editor
test_calculator_sends_message_when_input_is_a_list
I want to see what happens when I send a list as input to the calculator program, will it send a message or raise TypeError?
RED: make it fail
I add a test to see what happens when I send a list as input
88 self.assertEqual(
89 src.calculator.subtract('1', '1'),
90 error_message
91 )
92
93 def test_calculator_sends_message_when_input_is_a_list(self):
94 a_list = [0, 1, 2, 3]
95
96 self.assertEqual(
97 src.calculator.add(a_list, 0),
98 'BOOM!!!'
99 )
100
101
102# Exceptions seen
the terminal shows AssertionError
AssertionError: 'Excuse me?! Numbers only! try again...' != 'BOOM!!!'
GREEN: make it pass
I change the expectation to match
96 self.assertEqual(
97 src.calculator.add(a_list, 0),
98 'Excuse me?! Numbers only! try again...'
99 )
the test passes
REFACTOR: make it better
I add another assertion for the next function
96 self.assertEqual( 97 src.calculator.add(a_list, 0), 98 'Excuse me?! Numbers only! try again...' 99 ) 100 self.assertEqual( 101 src.calculator.divide(a_list, 1), 102 'BAP!!!' 103 )the terminal shows AssertionError
AssertionError: 'Excuse me?! Numbers only! try again...' != 'BAP!!!'I change the expectation
101 self.assertEqual( 102 src.calculator.divide(a_list, 1), 103 'Excuse me?! Numbers only! try again...' 104 )the test passes. Wait a minute! I just wrote the same thing twice, and I did it 8 times before in test_calculator_sends_message_when_input_is_not_a_number and 2 times in the
only_takes_numbersfunction. Never againI add a variable to remove the repetition
93 def test_calculator_sends_message_when_input_is_a_list(self): 94 a_list = [0, 1, 2, 3] 95 error_message = 'Excuse me?! Numbers only! try again...' 96 97 self.assertEqual( 98 src.calculator.add(a_list, 0), 99 error_message 100 ) 101 self.assertEqual( 102 src.calculator.divide(a_list, 1), 103 error_message 104 )the test is still green
how to multiply a list
I add an assertion for the multiply function
101 self.assertEqual( 102 src.calculator.divide(a_list, 1), 103 error_message 104 ) 105 self.assertEqual( 106 src.calculator.multiply(a_list, 2), 107 'BOOM!!!' 108 )the terminal shows AssertionError
AssertionError: [0, 1, 2, 3, 0, 1, 2, 3] != 'BOOM!!!'I change the expectation of the test to the error message
105 self.assertEqual( 106 src.calculator.multiply(a_list, 2), 107 error_message 108 )the terminal shows AssertionError
AssertionError: [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] != 'Excuse me?! Numbers only! try again...'I open
calculator.pyin the editorI add an if statement to the
only_takes_numbersfunction incalculator.py1def only_takes_numbers(function): 2 def wrapper(first_input, second_input): 3 error_message = 'Excuse me?! Numbers only! try again...' 4 5 if isinstance(first_input, str) or isinstance(second_input, str): 6 return error_message 7 if isinstance(first_input, list) or isinstance(second_input, list): 8 return error_message 9 10 try: 11 return function(first_input, second_input) 12 except TypeError: 13 return error_message 14 return wrapperthe test passes. The
only_takes_numbersfunction looks ugly now, there has to be a better wayI add an assertion for the subtract function to
test_calculator.py105 self.assertEqual( 106 src.calculator.multiply(a_list, 2), 107 error_message 108 ) 109 self.assertEqual( 110 src.calculator.subtract(a_list, 3), 111 'BOOM!!!' 112 )the terminal shows AssertionError
AssertionError: 'Excuse me?! Numbers only! try again...' != 'BOOM!!!'I change the expectation to match
109 self.assertEqual( 110 src.calculator.subtract(a_list, 3), 111 error_message 112 )the test passes
I remove the name of the test to move the new assertions to
test_calculator_sends_message_when_input_is_not_a_number88 self.assertEqual( 89 src.calculator.subtract('1', '1'), 90 error_message 91 ) 92 93 two_numbers = [0, 1, 2, 3] 94 error_message = 'Excuse me?! Numbers only! try again...' 95 96 self.assertEqual( 97 src.calculator.add(a_list, 0), 98 error_message 99 ) 100 self.assertEqual( 101 src.calculator.divide(a_list, 1), 102 error_message 103 ) 104 self.assertEqual( 105 src.calculator.multiply(a_list, 2), 106 error_message 107 ) 108 self.assertEqual( 109 src.calculator.subtract(a_list, 3), 110 error_message 111 ) 112 113 114# Exceptions seenthe tests are still green
I remove the duplication of the
error_messagevariable88 self.assertEqual( 89 src.calculator.subtract('1', '1'), 90 error_message 91 ) 92 93 a_list = [0, 1, 2, 3] 94 95 self.assertEqual( 96 src.calculator.add(a_list, 0), 97 error_message 98 )still green. This test is long, there has to be a better way to test the calculator with inputs that are NOT numbers
how to test if something is an instance of more than one type
The isinstance function can take a tuple as the second input, which allows me to check if the first input is an instance of any of the objects in the tuple
I add a variable to the
only_takes_numbersfunction incalculator.py1def only_takes_numbers(function): 2 def wrapper(first_input, second_input): 3 bad_types = (str, list) 4 error_message = 'Excuse me?! Numbers only! try again...' 5 6 # if isinstance(first_input, str) or isinstance(second_input, str): 7 # return error_message 8 # if isinstance(first_input, list) or isinstance(second_input, list): 9 # return error_message 10 11 if isinstance(first_input, bad_types) or isinstance(second_input, bad_types): 12 return error_message 13 14 try: 15 return function(first_input, second_input) 16 except TypeError: 17 return error_message 18 return wrapperthe tests are still green
I remove the comments
1def only_takes_numbers(function): 2 def wrapper(first_input, second_input): 3 bad_types = (str, list) 4 error_message = 'Excuse me?! Numbers only! try again...' 5 6 if isinstance(first_input, bad_types) or isinstance(second_input, bad_types): 7 return error_message 8 9 try: 10 return function(first_input, second_input) 11 except TypeError: 12 return error_message 13 return wrapperstill green
I can use Logical Negation (NOT) to make the if statement allow only the types of numbers (integers and floats) that I want the calculator to work with
1def only_takes_numbers(function): 2 def wrapper(first_input, second_input): 3 good_types = (int, float) 4 bad_types = (str, list) 5 error_message = 'Excuse me?! Numbers only! try again...' 6 7 # if isinstance(first_input, bad_types) or isinstance(second_input, bad_types): 8 # return error_message 9 10 if not isinstance(first_input, good_types) or not isinstance(second_input, good_types): 11 return error_message 12 else: 13 try: 14 return function(first_input, second_input) 15 except TypeError: 16 return error_message 17 return wrapperthe tests are still green
I remove the comments and the
bad_typesvariable1def only_takes_numbers(function): 2 def wrapper(first_input, second_input): 3 good_types = (int, float) 4 error_message = 'Excuse me?! Numbers only! try again...' 5 6 if not isinstance(first_input, good_types) or not isinstance(second_input, good_types): 7 return error_message 8 else: 9 try: 10 return function(first_input, second_input) 11 except TypeError: 12 return error_message 13 return wrapperthe tests are still passing
“not” happens twice in the if statement, I write the line in terms of it
6 # if not isinstance(first_input, good_types) or not isinstance(second_input, good_types): 7 if (not isinstance(first_input, good_types)) (not and) (not isinstance(second_input, good_types)): 8 return error_messagethe terminal shows SyntaxError
SyntaxError: invalid syntaxI add SyntaxError to the list of Exceptions in
test_calculator.py113# Exceptions seen 114# AssertionError 115# NameError 116# AttributeError 117# TypeError 118# ZeroDivisionError 119# SyntaxErrorI fix the if statement<if statement> in
calculator.py6 # if not isinstance(first_input, good_types) or not isinstance(second_input, good_types): 7 # if (not isinstance(first_input, good_types)) (not and) ((not isinstance(second_input, good_types))): 8 if not (isinstance(first_input, good_types) and isinstance(second_input, good_types)): 9 return error_messagethe test is green again
I remove the comments
1def only_takes_numbers(function): 2 def wrapper(first_input, second_input): 3 good_types = (int, float) 4 error_message = 'Excuse me?! Numbers only! try again...' 5 6 if not (isinstance(first_input, good_types) and isinstance(second_input, good_types)): 7 return error_message 8 else: 9 try: 10 return function(first_input, second_input) 11 except TypeError: 12 return error_message 13 return wrapperall the tests are still passing. I wonder if there is a way to write this function with only one return statement for the error message
test_calculator_w_list_items
I can use a list to test the calculator functions as long as its items are numbers
RED: make it fail
I add a new test to use the index of the items in the list to test the calculator
107 self.assertEqual(
108 src.calculator.subtract(a_list, 3),
109 error_message
110 )
111
112 def test_calculator_w_list_items(self):
113 two_numbers = [self.random_first_number, self.random_second_number]
114
115 self.assertEqual(
116 src.calculator.add(two_numbers[0], two_numbers[1]),
117 self.random_first_number-self.random_second_number
118 )
119
120
121 # Exceptions seen
the terminal shows AssertionError
AssertionError: ABC.DEFGHIJKLMNOPQ != RST.UVWXYZABCDEFG
GREEN: make it pass
I change the expectation to the right calculation
115 self.assertEqual(
116 src.calculator.add(two_numbers[0], two_numbers[1]),
117 self.random_first_number+self.random_second_number
118 )
the test passes
REFACTOR: make it better
I add an assertion for the divide function
115 self.assertEqual( 116 src.calculator.add(two_numbers[0], two_numbers[1]), 117 self.random_first_number+self.random_second_number 118 ) 119 self.assertEqual( 120 src.calculator.divide(two_numbers[-2], two_numbers[-1]), 121 self.random_first_number*self.random_second_number 122 )the terminal shows AssertionError
AssertionError: D.EFGHIJKLMNOPQRST != UVWXY.ZABCDEFGHIJI change the calculation
119 self.assertEqual( 120 src.calculator.divide(two_numbers[-2], two_numbers[-1]), 121 self.random_first_number/self.random_second_number 122 )the test passes
I add another assertion
119 self.assertEqual( 120 src.calculator.divide(two_numbers[-2], two_numbers[-1]), 121 self.random_first_number/self.random_second_number 122 ) 123 self.assertEqual( 124 src.calculator.multiply(two_numbers[1], two_numbers[-1]), 125 self.random_first_number*self.random_second_number 126 )the terminal shows AssertionError
AssertionError: EFGHIJ.KLMNOPQRSTU != VWXYZ.ABCDEFGHIJKLI change the expectation
123 self.assertEqual( 124 src.calculator.multiply(two_numbers[1], two_numbers[-1]), 125 self.random_second_number*self.random_second_number 126 )the test passes
I add an assertion for the subtract function
123 self.assertEqual( 124 src.calculator.multiply(two_numbers[1], two_numbers[-1]), 125 self.random_second_number*self.random_second_number 126 ) 127 self.assertEqual( 128 src.calculator.subtract(two_numbers[-2], two_numbers[0]), 129 self.random_first_number-self.random_second_number 130 )the terminal shows AssertionError
AssertionError: 0.0 != FGH.IJKLMNOPQRSTUI change the expectation to match
127 self.assertEqual( 128 src.calculator.subtract(two_numbers[-2], two_numbers[0]), 129 self.random_first_number-self.random_first_number 130 )the test passes
Python allows me use a star expression like I did in test_functions_w_unknown_arguments. I add an assertion with it
127 self.assertEqual( 128 src.calculator.subtract(two_numbers[-2], two_numbers[0]), 129 self.random_first_number-self.random_first_number 130 ) 131 self.assertEqual( 132 src.calculator.add(*two_numbers), 133 self.random_first_number-self.random_second_number 134 )the terminal shows AssertionError
AssertionError: GHI.JKLMNOPQRSTUVW != XYZ.ABCDEFGHIJKLMNI change the expectation
131 self.assertEqual( 132 src.calculator.add(*two_numbers), 133 self.random_first_number+self.random_second_number 134 )the test passes
I add another assertion
131 self.assertEqual( 132 src.calculator.add(*two_numbers), 133 self.random_first_number+self.random_second_number 134 ) 135 self.assertEqual( 136 src.calculator.divide(*two_numbers), 137 self.random_first_number*self.random_second_number 138 )the terminal shows AssertionError
AssertionError: H.IJKLMNOPQRSTUVWX != YZABCD.EFGHIJKLMNOI change the calculation
135 self.assertEqual( 136 src.calculator.divide(*two_numbers), 137 self.random_first_number/self.random_second_number 138 )the test passes
I add an assertion for the multiply function
135 self.assertEqual( 136 src.calculator.divide(*two_numbers), 137 self.random_first_number/self.random_second_number 138 ) 139 self.assertEqual( 140 src.calculator.multiply(*two_numbers), 141 self.random_first_number/self.random_second_number 142 )the terminal shows AssertionError
AssertionError: IJKLMN.OPQRSTUVWX != Y.ZABCDEFGHIJKLMNOPI change the calculation
139 self.assertEqual( 140 src.calculator.multiply(*two_numbers), 141 self.random_first_number*self.random_second_number 142 )the test passes
I add the next assertion
139 self.assertEqual( 140 src.calculator.multiply(*two_numbers), 141 self.random_first_number*self.random_second_number 142 ) 143 self.assertEqual( 144 src.calculator.subtract(*two_numbers), 145 self.random_first_number+self.random_second_number 146 )the terminal shows AssertionError
AssertionError: JKL.MNOPQRSTUVWXYZ != ABC.DEFGHIJKLMNOPI change the expectation
143 self.assertEqual( 144 src.calculator.subtract(*two_numbers), 145 self.random_first_number-self.random_second_number 146 )the test passes
test_calculator_raises_type_error_when_given_more_than_two_inputs
It is important to note that the star expression always gives the items from the list in order, and I cannot use a list that has more than 2 numbers with these calculator functions since they only take 2 inputs
RED: make it fail
I add a new test to show the problem when I have more than 2 inputs and use a star expression
143 self.assertEqual(
144 src.calculator.subtract(*a_list),
145 self.random_first_number-self.random_second_number
146 )
147
148 def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
149 not_two_numbers = [0, 1, 2]
150
151 src.calculator.add(*not_two_numbers)
152
153
154# Exceptions seen
TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were given
GREEN: make it pass
I add the assertRaises method to handle the Exception
148 def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
149 not_two_numbers = [0, 1, 2]
150
151 with self.assertRaises(TypeError):
152 src.calculator.add(*not_two_numbers)
the test passes
REFACTOR: make it better
I add a failing line for division with the new list
151 with self.assertRaises(TypeError): 152 src.calculator.add(*not_two_numbers) 153 src.calculator.divide(*not_two_numbers)TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were givenI add assertRaises
151 with self.assertRaises(TypeError): 152 src.calculator.add(*not_two_numbers) 153 with self.assertRaises(TypeError): 154 src.calculator.divide(*not_two_numbers) 155 156the test passesI add a line for multiplication
153 with self.assertRaises(TypeError): 154 src.calculator.divide(*not_two_numbers) 155 src.calculator.multiply(*not_two_numbers)TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were givenI add assertRaises
153 with self.assertRaises(TypeError): 154 src.calculator.divide(*not_two_numbers) 155 with self.assertRaises(TypeError): 156 src.calculator.multiply(*not_two_numbers)the test passes
I add the last line
155 with self.assertRaises(TypeError): 156 src.calculator.multiply(*not_two_numbers) 157 src.calculator.subtract(*not_two_numbers)TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were givenI handle the Exception
148 def test_calculator_raises_type_error_when_given_more_than_two_inputs(self): 149 not_two_numbers = [0, 1, 2] 150 151 with self.assertRaises(TypeError): 152 src.calculator.add(*not_two_numbers) 153 with self.assertRaises(TypeError): 154 src.calculator.divide(*not_two_numbers) 155 with self.assertRaises(TypeError): 156 src.calculator.multiply(*not_two_numbers) 157 with self.assertRaises(TypeError): 158 src.calculator.subtract(*not_two_numbers)the test passes
close the project
I close
test_calculator.pyandcalculator.pyin the editorsI click in the terminal and exit the tests with ctrl+c on the keyboard, the terminal shows
(.venv) .../pumping_python/calculatorI deactivate the virtual environment
deactivatethe terminal goes back to the command line,
(.venv)is no longer on the left side.../pumping_python/calculatorI change directory to the parent of
calculatorcd ..the terminal shows
.../pumping_pythonI am back in the
pumping_pythondirectory
review
I added the following tests to the calculator program after testing lists
code from the chapter
what is next?
you know
would you like to test list comprehensions? They are a quick way to make lists
rate pumping python
If this has been a 7 star experience for you, please leave a 5 star review. It helps other people get into the book too