how to make a calculator 7
I can use a dictionary to test the calculator functions as long as its values are numbers
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 7 items tests/test_calculator.py ....... [100%] ============================ 7 passed in X.YZs =============================I hold ctrl on the keyboard and click on
tests/test_calculator.pyto open it in the editor
test_calculator_w_dictionary_items
RED: make it fail
I add a new test to use a dictionary to test the calculator
119 self.assertEqual(
120 src.calculator.subtract(*a_list),
121 self.random_first_number-self.random_second_number
122 )
123
124 def test_calculator_w_dictionary_items(self):
125 two_numbers = {
126 'x': self.random_first_number,
127 'y': self.random_second_number,
128 }
129
130 self.assertEqual(
131 src.calculator.add(two_numbers['x'], two_numbers['y']),
132 self.random_first_number+self.random_first_number
133 )
134
135 def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
136 not_two_numbers = [0, 1, 2]
the terminal shows AssertionError
AssertionError: ABC.DEFGHIJKLMNOPQ != RST.UVWXYZABCDEFG
GREEN: make it pass
I change the expectation to the right calculation
130 self.assertEqual(
131 src.calculator.add(two_numbers['x'], two_numbers['y']),
132 self.random_first_number+self.random_second_number
133 )
the test passes
REFACTOR: make it better
I add an assertion for the divide function
130 self.assertEqual( 131 src.calculator.add(two_numbers['x'], two_numbers['y']), 132 self.random_first_number+self.random_second_number 133 ) 134 self.assertEqual( 135 src.calculator.divide(two_numbers['x'], two_numbers['y']), 136 self.random_first_number*self.random_second_number 137 )the terminal shows AssertionError
AssertionError: D.EFGHIJKLMNOPQRST != UVWXY.ZABCDEFGHIJI change the calculation
134 self.assertEqual( 135 src.calculator.divide(two_numbers['x'], two_numbers['y']), 136 self.random_first_number/self.random_second_number 137 )the test passes
I add another assertion
134 self.assertEqual( 135 src.calculator.divide(two_numbers['x'], two_numbers['y']), 136 self.random_first_number/self.random_second_number 137 ) 138 self.assertEqual( 139 src.calculator.multiply(two_numbers['y'], two_numbers['y']), 140 self.random_first_number*self.random_second_number 141 )the terminal shows AssertionError
AssertionError: EFGHIJ.KLMNOPQRSTU != VWXYZ.ABCDEFGHIJKLI change the expectation
123 self.assertEqual( 124 src.calculator.multiply(two_numbers['y'], two_numbers['y']), 125 self.random_second_number*self.random_second_number 126 )the test passes
I add an assertion for the subtract function
138 self.assertEqual( 139 src.calculator.multiply(two_numbers['y'], two_numbers['y']), 140 self.random_second_number*self.random_second_number 141 ) 142 self.assertEqual( 143 src.calculator.subtract(two_numbers['x'], two_numbers['x']), 144 self.random_first_number-self.random_second_number 145 )the terminal shows AssertionError
AssertionError: 0.0 != FGH.IJKLMNOPQRSTUI change the expectation to match
142 self.assertEqual( 143 src.calculator.subtract(two_numbers['x'], two_numbers['x']), 144 self.random_first_number-self.random_first_number 145 )the test passes
Python allows me use a double starred expression like I did in test_functions_w_unknown_arguments. I add an assertion with it
142 self.assertEqual( 143 src.calculator.subtract(two_numbers['x'], two_numbers['x']), 144 self.random_first_number-self.random_first_number 145 ) 146 self.assertEqual( 147 src.calculator.add(**two_numbers), 148 self.random_first_number-self.random_second_number 149 )TypeError: only_takes_numbers.<locals>.wrapper() got an unexpected keyword argument 'x'the names of the keys in the
two_numbersdictionary must be the same as the names of the arguments the calculator functions receive -first_inputandsecond_inputnotxandy. I changexandytofirst_inputandsecond_inputin the test124 def test_calculator_w_dictionary_items(self): 125 two_numbers = { 126 'first_input': self.random_first_number, 127 'second_input': self.random_second_number, 128 } 129 130 self.assertEqual( 131 src.calculator.add( 132 two_numbers['first_input'], 133 two_numbers['second_input'] 134 ), 135 self.random_first_number+self.random_second_number 136 ) 137 self.assertEqual( 138 src.calculator.divide( 139 two_numbers['first_input'], 140 two_numbers['second_input'] 141 ), 142 self.random_first_number/self.random_second_number 143 ) 144 self.assertEqual( 145 src.calculator.multiply( 146 two_numbers['second_input'], 147 two_numbers['second_input'] 148 ), 149 self.random_second_number*self.random_second_number 150 ) 151 self.assertEqual( 152 src.calculator.subtract( 153 two_numbers['first_input'], 154 two_numbers['first_input'] 155 ), 156 self.random_first_number-self.random_first_number 157 )the terminal shows AssertionError
AssertionError: VWX.YZABCDEFGHIJK != LMN.OPQRSTUVWXYZABCI change the calculation in the assertion
158 self.assertEqual( 159 src.calculator.add(**two_numbers), 160 self.random_first_number+self.random_second_number 161 )the test passes
I add another assertion
158 self.assertEqual( 159 src.calculator.add(**two_numbers), 160 self.random_first_number+self.random_second_number 161 ) 162 self.assertEqual( 163 src.calculator.divide(**two_numbers), 164 self.random_first_number*self.random_second_number 165 )the terminal shows AssertionError
AssertionError: H.IJKLMNOPQRSTUVWX != YZABCD.EFGHIJKLMNOI change the calculation
162 self.assertEqual( 163 src.calculator.divide(**two_numbers), 164 self.random_first_number/self.random_second_number 165 )the test passes
I add an assertion for the multiply function
162 self.assertEqual( 163 src.calculator.divide(**two_numbers), 164 self.random_first_number/self.random_second_number 165 ) 166 self.assertEqual( 167 src.calculator.multiply(**two_numbers), 168 self.random_first_number/self.random_second_number 169 )the terminal shows AssertionError
AssertionError: IJKLMN.OPQRSTUVWX != Y.ZABCDEFGHIJKLMNOPI change the calculation
166 self.assertEqual( 167 src.calculator.multiply(**two_numbers), 168 self.random_first_number*self.random_second_number 169 )the test passes
I add the next assertion
166 self.assertEqual( 167 src.calculator.multiply(**two_numbers), 168 self.random_first_number*self.random_second_number 169 ) 170 self.assertEqual( 171 src.calculator.subtract(**two_numbers), 172 self.random_first_number+self.random_second_number 173 )the terminal shows AssertionError
AssertionError: JKL.MNOPQRSTUVWXYZ != ABC.DEFGHIJKLMNOPI change the expectation
170 self.assertEqual( 171 src.calculator.subtract(**two_numbers), 172 self.random_first_number-self.random_second_number 173 )the test passes
I can use the values method to make a list to test the calculator in
test_calculator_w_list_items88 def test_calculator_w_list_items(self): 89 # two_numbers = [self.random_first_number, self.random_second_number] 90 a_dictionary = { 91 'x': self.random_first_number, 92 'y': self.random_second_number 93 } 94 two_numbers = list(a_dictionary.values()) 95 96 self.assertEqual( 97 src.calculator.add(two_numbers[0], two_numbers[1]), 98 self.random_first_number+self.random_second_number 99 )the test is still green
I can also use a dictionary with a for loop to make
test_calculator_sends_message_when_input_is_not_a_numbermore complex and simpler at the same time58 def test_calculator_sends_message_when_input_is_not_a_number(self): 59 error_message = 'brmph?! Numbers only. Try again...' 60 61 arithmetic = { 62 'addition': src.calculator.add, 63 'subtraction': src.calculator.subtract, 64 'multiplication': src.calculator.multiply, 65 'division': src.calculator.divide, 66 } 67 68 for data in ( 69 None, 70 True, False, 71 str(), 'text', 72 tuple(), (0, 1, 2, 'n'), 73 list(), [0, 1, 2, 'n'], 74 set(), {0, 1, 2, 'n'}, 75 dict(), {'key': 'value'}, 76 ): 77 with self.subTest(i=data): 78 for operation in arithmetic: 79 self.assertEqual( 80 arithmetic[operation](data, a_random_number()), 81 'BOOM!!!' 82 ) 83 self.assertEqual( 84 src.calculator.add(data, a_random_number()), 85 error_message 86 )the terminal shows AssertionError for every case in the iterable
SUBFAILED(i=None) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM' SUBFAILED(i=True) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM' SUBFAILED(i=False) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM' SUBFAILED(i='') tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM' SUBFAILED(i='text') tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM' SUBFAILED(i=()) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM' SUBFAILED(i=(0, 1, 2, 'n')) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM' SUBFAILED(i=[]) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM' SUBFAILED(i=[0, 1, 2, 'n']) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM' SUBFAILED(i=set()) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM' SUBFAILED(i={0, 1, 2, 'n'}) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM' SUBFAILED(i={}) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM' SUBFAILED(i={'key': 'value'}) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM'the test works
Note
arithmetic[operation](data, a_random_number())represents every operation in thearithmeticdictionary and every item in the iterable for examplearithmetic['addition'](None, a_random_number()) arithmetic['subtraction']((0, 1, 2, 'n'), a_random_number()) arithmetic['division'](dict(), a_random_number()) arithmetic['multiplication']('text', a_random_number())these 4 examples are translated by the computer to
src.calculator.add(None, a_random_number()) src.calculator.subtract((0, 1, 2, 'n'), a_random_number()) src.calculator.divide(dict(), a_random_number()) src.calculator.multiply('text', a_random_number())If I had to write a test for each operation and each data item, I would end up with a total of 52 tests. Too much
I change the expectation
77 with self.subTest(i=data): 78 for operation in arithmetic: 79 self.assertEqual( 80 arithmetic[operation](data, a_random_number), 81 error_message 82 )the test passes
I remove the other assertions
58 def test_calculator_sends_message_when_input_is_not_a_number(self): 59 error_message = 'brmph?! Numbers only. Try again...' 60 61 arithmetic = { 62 'addition': src.calculator.add, 63 'subtraction': src.calculator.subtract, 64 'multiplication': src.calculator.multiply, 65 'division': src.calculator.divide, 66 } 67 68 for data in ( 69 None, 70 True, False, 71 str(), 'text', 72 tuple(), (0, 1, 2, 'n'), 73 list(), [0, 1, 2, 'n'], 74 set(), {0, 1, 2, 'n'}, 75 dict(), {'key': 'value'}, 76 ): 77 with self.subTest(i=data): 78 for operation in arithmetic: 79 self.assertEqual( 80 arithmetic[operation](data, a_random_number()), 81 error_message 82 ) 83 self.assertEqual( 84 src.calculator.add(data, a_random_number()), 85 error_message 86 ) 87 88 def test_calculator_w_list_items(self):this solution is not as easy to read as what was there before especially for someone new to Python. There has to be a better way
test_calculator_functions
I want to use a dictionary to write one test that covers all the 4 calculator functions: addition, subtraction, division and multiplication
RED: make it pass
I add a new test
52 except ZeroDivisionError: 53 self.assertEqual( 54 src.calculator.divide(self.random_first_number, 0), 55 'brmph?! cannot divide by 0. Try again...' 56 ) 57 58 def test_calculator_functions(self): 59 arithmetic = { 60 'addition': src.calculator.add, 61 'subtraction': src.calculator.subtract, 62 'division': src.calculator.divide, 63 'multiplication': src.calculator.multiply, 64 } 65 66 for operation in arithmetic: 67 with self.subTest(operation=operation): 68 self.assertEqual( 69 arithmetic[operation]( 70 self.random_first_number, 71 self.random_second_number 72 ), 73 'BOOM!!!' 74 ) 75 76 def test_calculator_sends_message_when_input_is_not_a_number(self):the terminal shows AssertionError for the 4 arithmetic operations
SUBFAILED(operation='addition') tests/test_calculator.py::TestCalculator::test_calculator_functions - AssertionError: QRS.TUVWXYZABCDEF != 'BOOM!!!' SUBFAILED(operation='subtraction') tests/test_calculator.py::TestCalculator::test_calculator_functions - AssertionError: GHI.JKLMNOPQRSTUVWX != 'BOOM!!!' SUBFAILED(operation='division') tests/test_calculator.py::TestCalculator::test_calculator_functions - AssertionError: Y.ABCDEFGHIJKLMNOP != 'BOOM!!!' SUBFAILED(operation='multiplication') tests/test_calculator.py::TestCalculator::test_calculator_functions - AssertionError: QRSTUV.WXYZABCDEFG != 'BOOM!!!'
GREEN: make it pass
I need a way to add the calculations for each operation to the assertion. I add another dictionary
63 'multiplication': src.calculator.multiply, 64 } 65 expectations = { 66 'addition': ( 67 self.random_first_number+self.random_second_number 68 ), 69 'subtraction': ( 70 self.random_first_number-self.random_second_number 71 ), 72 'division': ( 73 self.random_first_number/self.random_second_number 74 ), 75 'multiplication': ( 76 self.random_first_number*self.random_second_number 77 ) 78 } 79 80 for operation in arithmetic:I use the new dictionary for the calculation in the assertion
80 for operation in arithmetic: 81 with self.subTest(operation=operation): 82 self.assertEqual( 83 arithmetic[operation]( 84 self.random_first_number, 85 self.random_second_number 86 ), 87 expectations[operation] 88 )the test passes.
This test goes through every operation in the arithmetic dictionary then calls the function that is its value with self.random_first_number and self.random_second_number as input, and checks if the result is the value for the key in the expectations dictionary
In other words
arithmetic['addition'](self.first_random_number, self.second_random_number) arithmetic['subtraction'](self.first_random_number, self.second_random_number) arithmetic['division'](self.first_random_number, self.second_random_number) arithmetic['multiplication'](self.first_random_number, self.second_random_number)these four statements get translated by the computer to
src.calculator.add(self.first_random_number, self.second_random_number) src.calculator.subtract(self.first_random_number, self.second_random_number) src.calculator.divide((self.first_random_number, self.second_random_number) src.calculator.multiply(self.first_random_number, self.second_random_number)then the computer checks if the results of the operations are the same as
expectations['addition'] expectations['subtraction'] expectations['division'] expectations['multiplication']which are
self.random_first_number+self.random_second_number self.random_first_number-self.random_second_number self.random_first_number/self.random_second_number self.random_first_number*self.random_second_numberthe test is checking if these statements are equal
for addition
src.calculator.add(self.first_random_number, self.second_random_number) self.random_first_number+self.random_second_numberfor subtraction
src.calculator.subtract(self.first_random_number, self.second_random_number) self.random_first_number-self.random_second_numberfor division
src.calculator.divide((self.first_random_number, self.second_random_number) self.random_first_number/self.random_second_numberfor multiplication
src.calculator.multiply(self.first_random_number, self.second_random_number) self.random_first_number*self.random_second_number
REFACTOR: make it better
the two dictionaries in this test have the same keys, I can put them together
75 'multiplication': ( 76 self.random_first_number*self.random_second_number 77 ) 78 } 79 80 arithmetic_tests = { 81 'addition': { 82 'function': src.calculator.add, 83 'expectation': ( 84 self.random_first_number+self.random_second_number 85 ), 86 }, 87 'subtraction': { 88 'function': src.calculator.subtract, 89 'expectation': ( 90 self.random_first_number-self.random_second_number 91 ), 92 }, 93 'division': { 94 'function': src.calculator.divide, 95 'expectation': ( 96 self.random_first_number/self.random_second_number 97 ), 98 }, 99 'multiplication': { 100 'function': src.calculator.divide, 101 'expectation': ( 102 self.random_first_number*self.random_second_number 103 ), 104 } 105 } 106 107 for operation in arithmetic:I add a new assertion in a for loop with the subTest method
107 for operation in arithmetic: 108 with self.subTest(operation=operation): 109 self.assertEqual( 110 arithmetic[operation]( 111 self.random_first_number, 112 self.random_second_number 113 ), 114 expectations[operation] 115 ) 116 117 for operation in arithmetic_tests: 118 with self.subTest(operation=operation): 119 self.assertEqual( 120 arithmetic_tests[operation]['function']( 121 self.random_first_number, 122 self.random_second_number 123 ), 124 'BOOM!!!' 125 ) 126 127 def test_calculator_sends_message_when_input_is_not_a_number(self):the terminal shows AssertionError:
SUBFAILED(operation='addition') tests/test_calculator.py::TestCalculator::test_calculator_functions - AssertionError: HIJ.KLMNOPQRSTUVW != 'BOOM!!!' SUBFAILED(operation='subtraction') tests/test_calculator.py::TestCalculator::test_calculator_functions - AssertionError: XYZA.BCDEFGHIJKLMN != 'BOOM!!!' SUBFAILED(operation='division') tests/test_calculator.py::TestCalculator::test_calculator_functions - AssertionError: NO.PQRSTUVWXYZABCDE != 'BOOM!!!' SUBFAILED(operation='multiplication') tests/test_calculator.py::TestCalculator::test_calculator_functions - AssertionError: FGHIJKLM.NOPQRSTUVW != 'BOOM!!!'I change the expectation
117 for operation in arithmetic_tests: 118 with self.subTest(operation=operation): 119 self.assertEqual( 120 arithmetic_tests[operation]['function']( 121 self.random_first_number, 122 self.random_second_number 123 ), 124 arithmetic_tests[operation]['expectation'] 125 )the test passes
I remove the other dictionaries and for loop
58 def test_calculator_functions(self): 59 arithmetic_tests = { 60 'addition': { 61 'function': src.calculator.add, 62 'expectation': ( 63 self.random_first_number+self.random_second_number 64 ), 65 }, 66 'subtraction': { 67 'function': src.calculator.subtract, 68 'expectation': ( 69 self.random_first_number-self.random_second_number 70 ), 71 }, 72 'division': { 73 'function': src.calculator.divide, 74 'expectation': ( 75 self.random_first_number/self.random_second_number 76 ), 77 }, 78 'multiplication': { 79 'function': src.calculator.multiply, 80 'expectation': ( 81 self.random_first_number*self.random_second_number 82 ), 83 } 84 } 85 86 for operation in arithmetic_tests: 87 with self.subTest(operation=operation): 88 self.assertEqual( 89 arithmetic_tests[operation]['function']( 90 self.random_first_number, 91 self.random_second_number 92 ), 93 arithmetic_tests[operation]['expectation'] 94 ) 95 96 def test_calculator_sends_message_when_input_is_not_a_number(self):I remove the
test_addition,test_subtractionandtest_multiplicationmethodsclass TestCalculator(unittest.TestCase): def setUp(self): self.random_first_number = a_random_number() self.random_second_number = a_random_number() def test_division(self):I need a way to handle ZeroDivisionError in
test_calculator_functionsfor the divide function. I changerandom_second_numberto0in the setUp method to make ZeroDivisionError happen in the tests12 def setUp(self): 13 self.random_first_number = a_random_number() 14 # self.random_second_number = a_random_number() 15 self.random_second_number = 0 16 17 def test_division(self):the terminal shows ZeroDivisionError for 3 tests
FAILED tests/test_calculator.py::TestCalculator::test_calculator_functions - ZeroDivisionError: float division by zero FAILED tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - ZeroDivisionError: float division by zero FAILED tests/test_calculator.py::TestCalculator::test_calculator_w_list_items - ZeroDivisionError: float division by zeroI use an exception handler to add a new class attribute (variable) to the setUp method for the result of division
15 self.random_second_number = 0 16 try: 17 self.division_result = ( 18 self.random_first_number / self.random_second_number 19 ) 20 except ZeroDivisionError: 21 self.division_result = 'brmph?! cannot divide by 0. Try again...'I use the new class attribute (variable) in
test_calculator_functions52 'division': { 53 'function': src.calculator.divide, 54 # 'expectation': ( 55 # self.random_first_number/self.random_second_number 56 # ), 57 'expectation': self.division_result, 58 },the terminal shows ZeroDivisionError for 2 tests
FAILED tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - ZeroDivisionError: float division by zero FAILED tests/test_calculator.py::TestCalculator::test_calculator_w_list_items - ZeroDivisionError: float division by zeroprogress
I add the class attribute (variable) to
test_calculator_w_list_items119 self.assertEqual( 120 src.calculator.divide(two_numbers[-2], two_numbers[-1]), 121 # self.random_first_number/self.random_second_number 122 self.division_result 123 )and
136 self.assertEqual( 137 src.calculator.divide(*two_numbers), 138 # self.random_first_number/self.random_second_number 139 self.division_result 140 )the terminal shows ZeroDivisionError for 1 test
FAILED tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - ZeroDivisionError: float division by zeroI add the class attribute (variable) to
test_calculator_w_dictionary_items163 self.assertEqual( 164 src.calculator.divide( 165 two_numbers['first_input'], 166 two_numbers['second_input'] 167 ), 168 # self.random_first_number/self.random_second_number 169 self.division_result 170 )and
189 self.assertEqual( 190 src.calculator.divide(**two_numbers), 191 # self.random_first_number/self.random_second_number 192 self.division_result 193 )the terminal shows green again. Lovely!
I remove the lines I commented out to replace with
self.division_resultI remove
test_division12 def setUp(self): 13 self.random_first_number = a_random_number() 14 # self.random_second_number = a_random_number() 15 self.random_second_number = 0 16 try: 17 self.division_result = ( 18 self.random_first_number / self.random_second_number 19 ) 20 except ZeroDivisionError: 21 self.division_result = 'brmph?! cannot divide by 0. Try again...' 22 23 def test_calculator_functions(self):I change
self.random_second_numberback to a random float12def setUp(self): 13 self.random_first_number = a_random_number() 14 self.random_second_number = a_random_number()all tests are still green
the dictionaries in
test_calculator_functionsandtest_calculator_sends_message_when_input_is_not_a_numberare similar, I add a new dictionary in the setUp method to replace them19 except ZeroDivisionError: 20 self.division_result = 'brmph?! cannot divide by 0. Try again...' 21 22 self.arithmetic_tests = { 23 'addition': { 24 'function': src.calculator.add, 25 'expectation': ( 26 self.random_first_number+self.random_second_number 27 ), 28 }, 29 'subtraction': { 30 'function': src.calculator.subtract, 31 'expectation': ( 32 self.random_first_number-self.random_second_number 33 ), 34 }, 35 'division': { 36 'function': src.calculator.divide, 37 'expectation': self.division_result, 38 }, 39 'multiplication': { 40 'function': src.calculator.multiply, 41 'expectation': ( 42 self.random_first_number*self.random_second_number 43 ), 44 } 45 } 46 47 def test_calculator_functions(self):then I use it in
test_calculator_functions73 # for operation in arithmetic_tests: 74 for operation in self.arithmetic_tests: 75 with self.subTest(operation=operation): 76 self.assertEqual( 77 # arithmetic_tests[operation]['function']( 78 self.arithmetic_tests[operation]['function']( 79 self.random_first_number, 80 self.random_second_number 81 ), 82 # arithmetic_tests[operation]['expectation'] 83 self.arithmetic_tests[operation]['expectation'] 84 )the test is still green
I remove the commented lines and
arithemtic_testsdictionary fromtest_calculator_functions47 def test_calculator_functions(self): 48 for operation in self.arithmetic_tests: 49 with self.subTest(operation=operation): 50 self.assertEqual( 51 self.arithmetic_tests[operation]['function']( 52 self.random_first_number, 53 self.random_second_number 54 ), 55 self.arithmetic_tests[operation]['expectation'] 56 ) 57 58 def test_calculator_sends_message_when_input_is_not_a_number(self):still green
I use the new class attribute (variable) in
test_calculator_sends_message_when_input_is_not_a_number77 with self.subTest(i=data): 78 # for operation in arithmetic: 79 for operation in self.arithmetic_tests: 80 self.assertEqual( 81 # arithmetic[operation](data, a_random_number()), 82 self.arithmetic_tests[operation]['function']( 83 data, a_random_number() 84 ), 85 error_message 86 )the test is still green
I remove the commented lines and the
arithmeticdictionary58 def test_calculator_sends_message_when_input_is_not_a_number(self): 59 error_message = 'brmph?! Numbers only. Try again...' 60 61 for data in ( 62 None, 63 True, False, 64 str(), 'text', 65 tuple(), (0, 1, 2, 'n'), 66 list(), [0, 1, 2, 'n'], 67 set(), {0, 1, 2, 'n'}, 68 dict(), {'key': 'value'}, 69 ): 70 with self.subTest(i=data): 71 for operation in self.arithmetic_tests: 72 self.assertEqual( 73 self.arithmetic_tests[operation]['function']( 74 data, a_random_number() 75 ), 76 error_message 77 ) 78 79 def test_calculator_w_list_items(self):still green
I add a for loop to use the
arithmetic_testsdictionary in test_calculator_raises_type_error_when_given_more_than_two_inputs180 with self.assertRaises(TypeError): 181 src.calculator.subtract(*not_two_numbers) 182 183 for operation in self.arithmetic_tests: 184 with self.subTest(operation=operation): 185 self.arithmetic_tests[operation]['function']( 186 **not_two_numbers 187 ) 188 189 190# Exceptions seenthe terminal shows TypeError for all 4 cases
SUBFAILED(operation='addition') tests/test_calculator.py::TestCalculator::test_calculator_raises_type_error_when_given_more_than_two_inputs - TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were ... SUBFAILED(operation='subtraction') tests/test_calculator.py::TestCalculator::test_calculator_raises_type_error_when_given_more_than_two_inputs - TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were ... SUBFAILED(operation='division') tests/test_calculator.py::TestCalculator::test_calculator_raises_type_error_when_given_more_than_two_inputs - TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were ... SUBFAILED(operation='multiplication') tests/test_calculator.py::TestCalculator::test_calculator_raises_type_error_when_given_more_than_two_inputs - TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were ...I add the assertRaises method
183 for operation in self.arithmetic_tests: 184 with self.subTest(operation=operation): 185 with self.assertRaises(TypeError): 186 self.arithmetic_tests[operation]['function']( 187 *not_two_numbers 188 )the test passes
I remove the other assertions
171 def test_calculator_raises_type_error_when_given_more_than_two_inputs(self): 172 not_two_numbers = [0, 1, 2] 173 174 for operation in self.arithmetic_tests: 175 with self.subTest(operation=operation): 176 with self.assertRaises(TypeError): 177 self.arithmetic_tests[operation]['function']( 178 *not_two_numbers 179 ) 180 181 182# Exceptions seenstill green
I no longer need the variable, I remove it and use the list directly in the assertion
171 def test_calculator_raises_type_error_when_given_more_than_two_inputs(self): 172 for operation in self.arithmetic_tests: 173 with self.subTest(operation=operation): 174 with self.assertRaises(TypeError): 175 self.arithmetic_tests[operation]['function']( 176 *[0, 1, 2] 177 )the test is still green
Python allows me to put the 2 with statements together as one
171 def test_calculator_raises_type_error_when_given_more_than_two_inputs(self): 172 for operation in self.arithmetic_tests: 173 # with self.subTest(operation=operation): 174 # with self.assertRaises(TypeError): 175 with ( 176 self.subTest(operation=operation), 177 self.assertRaises(TypeError), 178 ): 179 self.arithmetic_tests[operation]['function']( 180 *[0, 1, 2] 181 )still green
I remove the commented lines
171 def test_calculator_raises_type_error_when_given_more_than_two_inputs(self): 172 for operation in self.arithmetic_tests: 173 with ( 174 self.subTest(operation=operation), 175 self.assertRaises(TypeError), 176 ): 177 self.arithmetic_tests[operation]['function']( 178 *[0, 1, 2] 179 )I add a for loop to
test_calculator_w_list_items115 self.assertEqual( 116 src.calculator.subtract(*two_numbers), 117 self.random_first_number-self.random_second_number 118 ) 119 120 for operation in self.arithmetic_tests: 121 with self.subTest(operation=operation): 122 self.assertEqual( 123 self.arithmetic_tests[operation]['function']( 124 *two_numbers 125 ), 126 'BOOM!!!' 127 ) 128 129 def test_calculator_w_dictionary_items(self):the terminal shows AssertionError for the 4 operations
SUBFAILED(operation='addition') tests/test_calculator.py::TestCalculator::test_calculator_w_list_items - AssertionError: YZA.BCDEFGHIJKLMNO != 'BOOM!!!' SUBFAILED(operation='subtraction') tests/test_calculator.py::TestCalculator::test_calculator_w_list_items - AssertionError: PQR.STUVWXYZABCDE != 'BOOM!!!' SUBFAILED(operation='division') tests/test_calculator.py::TestCalculator::test_calculator_w_list_items - AssertionError: F.GHIJKLMNOPQRSTUV != 'BOOM!!!' SUBFAILED(operation='multiplication') tests/test_calculator.py::TestCalculator::test_calculator_w_list_items - AssertionError: WXYABC.DEFGHIJKLMN != 'BOOM!!!'I change the expectation
20 for operation in self.arithmetic_tests: 21 with self.subTest(operation=operation): 22 self.assertEqual( 23 self.arithmetic_tests[operation]['function']( 24 *two_numbers 25 ), 26 self.arithmetic_tests[operation]['expectation'] 27 )the test passes
I remove all the assertions for the starred expression
99 self.assertEqual( 100 src.calculator.subtract(two_numbers[-2], two_numbers[0]), 101 self.random_first_number-self.random_first_number 102 ) 103 104 for operation in self.arithmetic_tests: 105 with self.subTest(operation=operation): 106 self.assertEqual( 107 self.arithmetic_tests[operation]['function']( 108 *two_numbers 109 ), 110 self.arithmetic_tests[operation]['expectation'] 111 ) 112 113 def test_calculator_w_dictionary_items(self):still green
I add a for loop to
test_calculator_w_dictionary_items159 self.assertEqual( 160 src.calculator.subtract(**two_numbers), 161 self.random_first_number-self.random_second_number 162 ) 163 164 for operation in self.arithmetic_tests: 165 with self.subTest(operation=operation): 166 self.assertEqual( 167 self.arithmetic_tests[operation]['function']( 168 **two_numbers 169 ), 170 'BOOM!!!' 171 ) 172 173 def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):the terminal shows AssertionError for the 4 operations
SUBFAILED(operation='addition') tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - AssertionError: OPQ.RSTUVWXYZABCDEF != 'BOOM!!!' SUBFAILED(operation='subtraction') tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - AssertionError: GHI.JKLMNOPQRSTUVWX != 'BOOM!!!' SUBFAILED(operation='division') tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - AssertionError: Y.ZABCDEFGHIJKLMNOP != 'BOOM!!!' SUBFAILED(operation='multiplication') tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - AssertionError: -QRSTU.VWXYZABCDEF != 'BOOM!!!'I change the expectation
164 for operation in self.arithmetic_tests: 165 with self.subTest(operation=operation): 166 self.assertEqual( 167 self.arithmetic_tests[operation]['function']( 168 **two_numbers 169 ), 170 self.arithmetic_tests[operation]['expectation'] 171 )the test passes
I remove the other assertions for the double starred expression
140 self.assertEqual( 141 src.calculator.subtract( 142 two_numbers['first_input'], 143 two_numbers['first_input'] 144 ), 145 self.random_first_number-self.random_first_number 146 ) 147 148 for operation in self.arithmetic_tests: 149 with self.subTest(operation=operation): 150 self.assertEqual( 151 self.arithmetic_tests[operation]['function']( 152 **two_numbers 153 ), 154 self.arithmetic_tests[operation]['expectation'] 155 ) 156 157 def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):still green
test squares
Since I am using a dictionary adding a new test is easy. I want to add a test for using the calculator to square numbers like I did in test_making_a_list_w_processes
RED: make it fail
I add a new key for the square function to the arithmetic_tests dictionary
40 'multiplication': {
41 'function': src.calculator.multiply,
42 'expectation': (
43 self.random_first_number*self.random_second_number
44 ),
45 },
46 'square': {
47 'function': src.calculator.square,
48 'expectation': self.random_first_number**2
49 },
50 }
51
52 def test_calculator_functions(self):
the terminal shows AttributeError for every test
AttributeError: module 'src.calculator' has no attribute 'square'
GREEN: make it pass
I open
calculator.pyin the editor then add a new function36@only_takes_numbers 37def add(first_input, second_input): 38 return first_input + second_input 39 40 41def square(first_input): 42 return NoneTypeError: square() takes 1 positional argument but 2 were givenI add a second input argument
41def square(first_input, second_input): 42 return Nonethe terminal shows AssertionError for 16 tests
I change the return statement
41def square(first_input, second_input): 42 return first_input**2the terminal shows AssertionError for 13 tests. Progress
I wrap the
squarefunction with theonly_takes_numbersdecorator function41@only_takes_numbers 42def square(first_input, second_input): 43 return first_input**2the test passes
:refactor:`REFACTOR`: make it better
I want the
squarefunction to only take one input since the second input is not used. I make the second input optional by adding a default argument41@only_takes_numbers 42def square(first_input, second_input=None): 43 return first_input**2still green. Not a beautiful solution but it works for now.
I use the
Rename Symbolfeature to change the name of thearithmetic_testsdictionary since I have added a test that is not arithmetic
20 except ZeroDivisionError: 21 self.division_result = 'brmph?! cannot divide by 0. Try again...' 22 23 self.calculator_tests = { 24 'addition': { 25 'function': src.calculator.add, 26 'expectation': ( 27 self.random_first_number+self.random_second_number 28 ), 29 },I think it is time to take nap. That was a lot.
close the project
I close
test_calculator.pyin the editorI 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 for the calculator program with dictionaries which made testing the program easier
code from the chapter
what is next?
you know
I edited makePythonTdd.sh or makePythonTdd.ps1 for the last few projects. I want to automate the process so that I can call the program and it does all the steps for me when I give it the name of the project.
Would you like to know how to make a Python Test Driven Development environment automatically with variables?
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