how to make a calculator 10: part 4
preview
These are the tests I have by the end of the chapter
1import random
2import streamlit.testing.v1
3import tests.test_calculator
4import unittest
5
6
7class TestStreamlitCalculator(unittest.TestCase):
8
9 def setUp(self):
10 self.tester = streamlit.testing.v1.AppTest.from_file(
11 'src/streamlit_calculator.py'
12 )
13 self.tester.run()
14
15 def test_streamlit_calculator_title(self):
16 self.assertEqual(self.tester.title[0].value, 'Calculator')
17
18 def test_streamlit_calculator_display(self):
19 self.assertEqual(
20 self.tester.main.children[1].type,
21 'flex_container'
22 )
23
24 def test_streamlit_calculator_columns(self):
25 self.assertEqual(len(self.tester.columns), 4)
26 self.assertEqual(
27 self.tester.columns[0].button('<-').label,
28 '<-'
29 )
30 self.assertEqual(
31 self.tester.columns[0].button('7').label,
32 '7'
33 )
34 self.assertEqual(
35 self.tester.columns[0].button('4').label,
36 '4'
37 )
38 self.assertEqual(
39 self.tester.columns[0].button('1').label,
40 '1'
41 )
42 self.assertEqual(
43 self.tester.columns[0].button('+/-').label,
44 '+/-'
45 )
46 self.assertEqual(
47 self.tester.columns[1].button('C').label,
48 'C'
49 )
50 self.assertEqual(
51 self.tester.columns[1].button('8').label,
52 '8'
53 )
54 self.assertEqual(
55 self.tester.columns[1].button('5').label,
56 '5'
57 )
58 self.assertEqual(
59 self.tester.columns[1].button('2').label,
60 '2'
61 )
62 self.assertEqual(
63 self.tester.columns[1].button('0').label,
64 '0'
65 )
66
67 self.assertEqual(
68 self.tester.columns[2].button('AC').label,
69 'AC'
70 )
71 self.assertEqual(
72 self.tester.columns[2].button('9').label,
73 '9'
74 )
75 self.assertEqual(
76 self.tester.columns[2].button('6').label,
77 '6'
78 )
79 self.assertEqual(
80 self.tester.columns[2].button('3').label,
81 '3'
82 )
83 self.assertEqual(
84 self.tester.columns[2].button('.').label,
85 '.'
86 )
87
88 self.assertEqual(
89 self.tester.columns[3].button('/').label,
90 '/'
91 )
92 self.assertEqual(
93 self.tester.columns[3].button('X').label,
94 'X'
95 )
96 self.assertEqual(
97 self.tester.columns[3].button('-').label,
98 r'\-'
99 )
100 self.assertEqual(
101 self.tester.columns[3].button('+').label,
102 r'\+'
103 )
104 self.assertEqual(
105 self.tester.columns[3].button('=').label,
106 '='
107 )
108
109 def test_streamlit_session_state(self):
110 expectation = '0'
111 for _ in range(0, 10):
112 number = random.choice('0123456789')
113 (
114 self.tester.button(number)
115 .click().run()
116 )
117 if expectation == '0':
118 expectation = number
119 else:
120 expectation += number
121 self.assertEqual(
122 self.tester.session_state['number'],
123 expectation
124 )
125
126 def test_streamlit_calculator_w_decimals(self):
127 for button in ('0.23.5.6.7.8.9'):
128 (
129 self.tester.button(button)
130 .click().run()
131 )
132 self.assertEqual(
133 self.tester.session_state['number'],
134 '.2356789'
135 )
136
137 def test_streamlit_calculator_w_plus_minus(self):
138 number = '963.0258741'
139 for button in number:
140 (
141 self.tester.button(button)
142 .click().run()
143 )
144 self.tester.button('+/-').click().run()
145 self.assertEqual(
146 self.tester.session_state['number'],
147 f'-{number}'
148 )
149
150 self.tester.session_state['number'] = '0'
151 number = '-963.0258741'
152 for button in number:
153 (
154 self.tester.button(button)
155 .click().run()
156 )
157
158 self.tester.button('+/-').click().run()
159 self.assertEqual(
160 self.tester.session_state['number'],
161 number[1:]
162 )
163
164 def test_streamlit_calculator_reset_state(self):
165 numbers = '123456789'
166 number = random.choice(numbers)
167 self.tester.button(number).click().run()
168 self.assertEqual(
169 self.tester.session_state['number'],
170 number
171 )
172 self.tester.button('C').click().run()
173 self.assertEqual(
174 self.tester.session_state['number'],
175 '0'
176 )
177
178 number = random.choice(numbers)
179 self.tester.button(number).click().run()
180 self.assertEqual(
181 self.tester.session_state['number'],
182 number
183 )
184 self.tester.button('AC').click().run()
185 self.assertEqual(
186 self.tester.session_state['number'],
187 '0'
188 )
189
190 @unittest.skip
191 def test_streamlit_calculator_operations(self):
192 # first_number = '1'
193 first_number = tests.test_calculator.a_random_number()
194 first_number = str(first_number)
195 second_number = '2'
196
197 for character in first_number:
198 if character == '-':
199 self.tester.button('+/-').click().run()
200 else:
201 self.tester.button(character).click().run()
202 self.tester.button('+').click().run()
203 self.assertEqual(
204 self.tester.session_state['first_number'],
205 first_number
206 )
207
208 self.tester.button(second_number).click().run()
209 self.tester.button('=').click().run()
210 self.assertEqual(
211 self.tester.session_state['second_number'],
212 second_number
213 )
214
215 self.assertEqual(
216 self.tester.session_state['number'],
217 str(float(first_number) + float(second_number))
218 )
219
220
221# Exceptions seen
222# NameError
223# AttributeError
224# AssertionError
225# SyntaxError
226# KeyError
227# streamlit.errors.StreamlitDuplicateElementKey
228# TypeError
open the project
I change directory to the
calculatorfoldercd calculatorI use
pytest-watcherto run the testsuv run pytest-watcher . --nowthe terminal is my friend, and shows
rootdir: .../pumping_python/calculator configfile: pyproject.toml collected 16 items tests/test_calculator.py ..... [ 31%] tests/test_calculator_website.py ... [ 50%] tests/test_streamlit_calculator.py ........ [100%] ======================== 16 passed in X.YZs =========================I open another terminal then use uv in the
calculatorfolderuv run streamlit run src/streamlit_calculator.pythe terminal is my friend, and shows
Collecting usage statistics. To deactivate, set browser.gatherUsageStats to false. You can now view your Streamlit app in your browser. Local URL: http://localhost:8501 Network URL: http://ABC.DEF.GHI.JKL:8501 External URL: http://MNO.PQR.STU.VWX:8501I use ctrl/option on the keyboard and click on
http://localhost:8501with the mouse to open the browser and it shows
test_streamlit_calculator_reset
I want the C and AC buttons to change the number the Calculator shows back to 0, they should reset the calculator.
RED: make it fail
I hold ctrl/command on the keyboard and click on
tests/test_streamlit_calculator.pywith the mouse to open it in the editorI add a test to
test_streamlit_calculator.py114 def test_streamlit_calculator_w_plus_minus(self): 115 a_number = '963.0258741' 116 for key in a_number: 117 self.press_button(key) 118 self.assertEqual( 119 self.tester.session_state['number'], a_number 120 ) 121 122 self.press_button('+/-') 123 self.assertEqual( 124 self.tester.session_state['number'], f'-{a_number}' 125 ) 126 127 self.press_button('+/-') 128 self.assertEqual( 129 self.tester.session_state['number'], a_number 130 ) 131 132 def test_streamlit_calculator_reset(self): 133 numbers = '123456789' 134 135 number = random.choice(numbers) 136 self.press_button(number) 137 self.assertEqual( 138 self.tester.session_state['number'], number 139 ) 140 141 self.press_button('C') 142 self.assertEqual( 143 self.tester.session_state['number'], '0' 144 ) 145 146 147# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: 'X' != '0'where
'X'is a random numbernumber = random.choice(numbers)picks a random number from'123456789'(thenumbersvariable)self.press_button(number)presses the random numberself.assertEqual(self.tester.session_state['number'], number)checks that the value of thenumberkey in the session state object is the same as the number that was pressedself.press_button('C')presses theCbuttonself.assertEqual(self.tester.session_state['number'], '0')checks that the thenumberkey in the session state object is set back to0after theCbutton is pressed
GREEN: make it pass
I add a function to
streamlit_calculator.py4def show_number(display): 5 display.write(streamlit.session_state['number']) 6 7 8def reset_number(): 9 streamlit.session_state['number'] = 0 10 11 12def plus_minus():I add the
on_clickandargsparameters to theCbutton in theadd_buttons_to_column_2function65def add_buttons_to_column_2(column_2, display): 66 column_2.button( 67 label='C', key='C', width='stretch', on_click=on_click, 68 args=[reset_number, display], type='primary', 69 ) 70 column_2.button( 71 label='8', key='8', width='stretch', on_click=on_click, 72 args=[add_number_to_state, display, '8'], 73 ) 74 column_2.button( 75 label='5', key='5', width='stretch', on_click=on_click, 76 args=[add_number_to_state, display, '5'], 77 ) 78 column_2.button( 79 label='2', key='2', width='stretch', on_click=on_click, 80 args=[add_number_to_state, display, '2'], 81 ) 82 column_2.button( 83 label='0', key='0', width='stretch', on_click=on_click, 84 args=[add_number_to_state, display, '0'], 85 ) 86 87 88def add_buttons_to_column_3(column_3, display):the terminal shows AssertionError
AssertionError: 0 != '0'I forgot that all the values have been strings so far
I change
0to'0'in thereset_numberfunction8def reset_number(): 9 streamlit.session_state['number'] = '0'the test passes
REFACTOR: make it better
I refresh the browser, click on number buttons and when I click on
Cit clears the numbers I typeI add an assertion for the
ACbutton in test_streamlit_calculator_reset intest_streamlit_calculator.py129 def test_streamlit_calculator_reset(self): 130 numbers = '123456789' 131 132 number = random.choice(numbers) 133 self.press_button(number) 134 self.assertEqual( 135 self.tester.session_state['number'], number 136 ) 137 138 self.press_button('C') 139 self.assertEqual( 140 self.tester.session_state['number'], '0' 141 ) 142 143 number = random.choice(numbers) 144 self.press_button(number) 145 self.assertEqual( 146 self.tester.session_state['number'], number 147 ) 148 149 self.press_button('AC') 150 self.assertEqual( 151 self.tester.session_state['number'], '0' 152 ) 153 154 155# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: 'X' != '0'I add the
on_clickandargsparameters to theACbutton in theadd_buttons_to_column_3function, instreamlit_calculator.py88def add_buttons_to_column_3(column_3, display): 89 column_3.button( 90 'AC', key='AC', width='stretch', on_click=on_click, 91 args=[reset, display], type='primary', 92 ) 93 column_3.button( 94 '9', key='9', width='stretch', on_click=on_click, 95 args=[add_number_to_state, display, '9'], 96 ) 97 column_3.button( 98 '6', key='6', width='stretch', on_click=on_click, 99 args=[add_number_to_state, display, '6'], 100 ) 101 column_3.button( 102 '3', key='3', width='stretch', on_click=on_click, 103 args=[add_number_to_state, display, '3'], 104 ) 105 column_3.button( 106 '.', key='.', width='stretch', on_click=on_click, 107 args=[add_decimal, display], 108 ) 109 110 111def add_buttons_to_column_4(column_4):the test passes
I refresh the browser, click on number buttons and when I click on
ACit clears the numbersI add a for loop to test_streamlit_calculator_reset in
test_streamlit_calculator.py132 def test_streamlit_calculator_reset(self): 133 numbers = '123456789' 134 135 for key in ('C', 'AC'): 136 with self.subTest(key=key): 137 138 number = random.choice(numbers) 139 self.press_button(number) 140 self.assertEqual( 141 self.tester.session_state['number'], 142 number 143 ) 144 145 self.press_button(key) 146 self.assertEqual( 147 self.tester.session_state['number'], 148 'BOOM!!!' 149 ) 150 151 number = random.choice(numbers) 152 self.press_button(number) 153 self.assertEqual( 154 self.tester.session_state['number'], number 155 )the terminal is my friend, and shows AssertionError
SUBFAILED(key='C') ... - AssertionError: '0' != 'BOOM!!!' SUBFAILED(key='AC') ... - AssertionError: '0' != 'BOOM!!!'I change the expectation
145 self.press_button(key) 146 self.assertEqual( 147 self.tester.session_state['number'], 148 '0' 149 )the test passes
I remove the other statements from test_streamlit_calculator_reset
132 def test_streamlit_calculator_reset(self): 133 numbers = '123456789' 134 135 for key in ('C', 'AC'): 136 with self.subTest(key=key): 137 138 number = random.choice(numbers) 139 self.press_button(number) 140 self.assertEqual( 141 self.tester.session_state['number'], 142 number 143 ) 144 145 self.press_button(key) 146 self.assertEqual( 147 self.tester.session_state['number'], 148 '0' 149 ) 150 151 152# Exceptions seen
on to the arithmetic operations
test_streamlit_calculator_operations
RED: make it fail
I add a test for a calculation in test_streamlit_calculator.py
145 self.press_button(key)
146 self.assertEqual(
147 self.tester.session_state['number'],
148 '0'
149 )
150
151 def test_streamlit_calculator_operations(self):
152 first_number = '1'
153 second_number = '2'
154
155 self.press_button(first_number)
156 self.press_button('+')
157 self.press_button(second_number)
158 self.press_button('=')
159
160 self.assertEqual(
161 self.tester.session_state['number'],
162 float(first_number)+float(second_number)
163 )
164
165
166# Exceptions seen
the terminal is my friend, and shows KeyError
KeyError: '+'
GREEN: make it pass
I change the key in the test
155 self.press_button(first_number) 156 self.press_button(r'\+') 157 self.press_button(second_number) 158 self.press_button('=')the terminal is my friend, and shows AssertionError
FAILED ... - AssertionError: '12' != 3.0The calculator is putting the two numbers together since they are strings. I want it to take the numbers separately then add them as numbers not strings. I add an assertion to check the value of the first number after the
+button is pressed151 def test_streamlit_calculator_operations(self): 152 first_number = '1' 153 second_number = '2' 154 155 self.press_button(first_number) 156 self.press_button(r'\+') 157 self.assertEqual( 158 self.tester.session_state['first_number'], 159 first_number 160 ) 161 162 self.press_button(second_number) 163 self.press_button('=') 164 165 self.assertEqual( 166 self.tester.session_state['number'], 167 float(first_number)+float(second_number) 168 )the terminal is my friend, and shows KeyError
KeyError: 'st.session_state has no key "first_number". Did you forget to initialize it? More info: https://docs.streamlit.io/develop/concepts/architecture/session-state#initialization'I use the setdefault method to add a default value for the
first_numberkey in themainfunction, instreamlit_calculator.py129def main(): 130 streamlit.title('Calculator') 131 streamlit.session_state.setdefault('number', '0') 132 streamlit.session_state.setdefault('first_number', '0') 133 display = streamlit.container(border=True) 134 135 column_1, column_2, column_3, operations = streamlit.columns(4) 136 add_buttons_to_column_1(column_1, display) 137 add_buttons_to_column_2(column_2, display) 138 add_buttons_to_column_3(column_3, display) 139 add_buttons_to_column_4(operations) 140 141 142if __name__ == '__main__':the terminal is my friend, and shows AssertionError
AssertionError: '0' != '1'I make a function to change the
first_numberkey of the session state object when the+button is pressed8def reset_number(): 9 streamlit.session_state['number'] = '0' 10 11 12def first_number(): 13 first_number = streamlit.session_state['number'] 14 streamlit.session_state['first_number'] = first_number 15 reset_number() 16 17 18def plus_minus():I add the
on_clickandargsparameters to the+button in theadd_buttons_to_column_4function117def add_buttons_to_column_4(column_4): 118 column_4.button( 119 label='/', key='/', width='stretch', type='primary', 120 ) 121 column_4.button( 122 label='X', key='X', width='stretch', type='primary', 123 ) 124 column_4.button( 125 label=r'\-', key=r'\-', width='stretch', type='primary', 126 ) 127 column_4.button( 128 label=r'\+', key=r'\+', width='stretch', type='primary', 129 ) 130 column_4.button( 131 label='=', key='=', width='stretch', type='primary', 132 on_click=on_click, args=[first_number, display], 133 ) 134 135 136def main():I add
displayas a positional argument of theadd_buttons_to_column_4function117def add_buttons_to_column_4(column_4, display):the terminal is my friend, and shows KeyError for more sub tests
I add
displayto the call to theadd_buttons_to_column_4function from themainfunction136def main(): 137 streamlit.title('Calculator') 138 streamlit.session_state.setdefault('number', '0') 139 streamlit.session_state.setdefault('first_number', '0') 140 display = streamlit.container(border=True) 141 142 column_1, column_2, column_3, operations = streamlit.columns(4) 143 add_buttons_to_column_1(column_1, display) 144 add_buttons_to_column_2(column_2, display) 145 add_buttons_to_column_3(column_3, display) 146 add_buttons_to_column_4(operations, display) 147 148 149if __name__ == '__main__':the terminal is my friend, and shows AssertionError
AssertionError: '2' != 3.0Okay! The calculator is no longer adding the strings, the error shows only the second number
I add an assertion for the second number after the
=button is pressed, intest_streamlit_calculator.py151 def test_streamlit_calculator_operations(self): 152 first_number = '1' 153 second_number = '2' 154 155 self.press_button(first_number) 156 self.press_button(r'\+') 157 self.assertEqual( 158 self.tester.session_state['first_number'], 159 first_number 160 ) 161 162 self.press_button(second_number) 163 self.press_button('=') 164 self.assertEqual( 165 self.tester.session_state['second_number'], 166 second_number 167 ) 168 169 self.assertEqual( 170 self.tester.session_state['number'], 171 float(first_number)+float(second_number) 172 )the terminal is my friend, and shows KeyError
KeyError: 'st.session_state has no key "second_number". Did you forget to initialize it? More info: https://docs.streamlit.io/develop/concepts/architecture/session-state#initialization'I use the setdefault method to add a default value for the
first_numberkey in themainfunction, instreamlit_calculator.py136def main(): 137 streamlit.title('Calculator') 138 streamlit.session_state.setdefault('number', '0') 139 streamlit.session_state.setdefault('first_number', '0') 140 streamlit.session_state.setdefault('second_number', '0') 141 display = streamlit.container(border=True) 142 143 column_1, column_2, column_3, operations = streamlit.columns(4) 144 add_buttons_to_column_1(column_1, display) 145 add_buttons_to_column_2(column_2, display) 146 add_buttons_to_column_3(column_3, display) 147 add_buttons_to_column_4(operations, display) 148 149 150if __name__ == '__main__':the terminal is my friend, and shows AssertionError
AssertionError: '0' != '2'I make a new function for the result of the calculation, in
streamlit_calculator.py12def first_number(): 13 first_number = streamlit.session_state['number'] 14 streamlit.session_state['first_number'] = first_number 15 reset_number() 16 17 18def calculate(): 19 second_number = streamlit.session_state['number'] 20 streamlit.session_state['second_number'] = second_number 21 reset_number() 22 23 24def plus_minus():I add the
on_clickandargsparameters to the=button in theadd_buttons_to_column_4function121def add_buttons_to_column_4(column_4, display): 122 column_4.button( 123 '/', key='/', width='stretch', type='primary', 124 ) 125 column_4.button( 126 'X', key='X', width='stretch', type='primary', 127 ) 128 column_4.button( 129 r'\-', key=r'\-', width='stretch', type='primary', 130 ) 131 column_4.button( 132 r'\+', key=r'\+', width='stretch', on_click=on_click, 133 args=[first_number, display], type='primary', 134 ) 135 column_4.button( 136 '=', key='=', width='stretch', on_click=on_click, 137 args=[calculate, display], type='primary', 138 )the terminal is my friend, and shows AssertionError
AssertionError: '0' != 3.0awesome! The
numberkey of the session state object now resets after the+and=buttons are pressedI add a calculation to the
calculatefunction18def calculate(): 19 second_number = streamlit.session_state['number'] 20 streamlit.session_state['second_number'] = second_number 21 streamlit.session_state['number'] = ( 22 streamlit.session_state['first_number'] 23 + streamlit.session_state['second_number'] 24 ) 25 26 27def plus_minus():the terminal is my friend, and shows AssertionError
AssertionError: '12' != 3.0I change the numbers to floats
4def calculate(): 5 second_number = streamlit.session_state['number'] 6 streamlit.session_state['second_number'] = second_number 7 streamlit.session_state['number'] = ( 8 float(streamlit.session_state['first_number']) 9 + float(streamlit.session_state['second_number']) 10 )the test passes
I refresh the browser and try the same calculation
the result is correct and the number looks different from the others I have seen so far
REFACTOR: make it better
when I try another number, the browser and the terminal for the application show TypeError
TypeError: unsupported operand type(s) for +=: 'float' and 'str'I want the result to look the same as the other numbers. I change it to a string in the
calculatefunction18def calculate(): 19 second_number = streamlit.session_state['number'] 20 streamlit.session_state['second_number'] = second_number 21 streamlit.session_state['number'] = str( 22 float(streamlit.session_state['first_number']) 23 + float(streamlit.session_state['second_number']) 24 ) 25 26 27def plus_minus():the terminal is my friend, and shows AssertionError
AssertionError: '3.0' != 3.0closer
I change the expectation of the assertion in test_streamlit_calculator_operations in
test_streamlit_calculator.py169 self.assertEqual( 170 self.tester.session_state['number'], 171 str(float(first_number)+float(second_number)) 172 )the test passes
I refresh the browser and try the calculation again
I like it, though I do not need the
.0after the3I add an assertion for subtraction
151 def test_streamlit_calculator_operations(self): 152 first_number = '1' 153 second_number = '2' 154 155 self.press_button(first_number) 156 self.press_button(r'\+') 157 self.assertEqual( 158 self.tester.session_state['first_number'], 159 first_number 160 ) 161 162 self.press_button(second_number) 163 self.press_button('=') 164 self.assertEqual( 165 self.tester.session_state['second_number'], 166 second_number 167 ) 168 169 self.assertEqual( 170 self.tester.session_state['number'], 171 str(float(first_number)+float(second_number)) 172 ) 173 174 self.press_button(first_number) 175 self.press_button(r'\-') 176 self.press_button(second_number) 177 self.press_button('=') 178 self.assertEqual( 179 self.tester.session_state['number'], 180 str(float(first_number)-float(second_number)) 181 ) 182 183 184# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: '4.0120000000000005' != '-1.0'I need a way to handle the operations
I add a variable then an assertion for an
'operation'key in the session state object151 def test_streamlit_calculator_operations(self): 152 first_number = '1' 153 second_number = '2' 154 155 operation = r'\+' 156 self.press_button(first_number) 157 # self.press_button(r'\+') 158 self.press_button(operation) 159 self.assertEqual( 160 self.tester.session_state['first_number'], 161 first_number 162 ) 163 self.assertEqual( 164 self.tester.session_state['operation'], 165 operation 166 ) 167 168 self.press_button(second_number) 169 self.press_button('=') 170 self.assertEqual( 171 self.tester.session_state['second_number'], 172 second_number 173 ) 174 175 self.assertEqual( 176 self.tester.session_state['number'], 177 str(float(first_number)+float(second_number)) 178 ) 179 180 self.press_button(first_number) 181 self.press_button(r'\-') 182 self.press_button(second_number) 183 self.press_button('=') 184 self.assertEqual( 185 self.tester.session_state['number'], 186 str(float(first_number)-float(second_number)) 187 )the terminal is my friend, and shows KeyError
KeyError: 'st.session_state has no key "operation". Did you forget to initialize it? More info: https://docs.streamlit.io/develop/concepts/architecture/session-state#initialization'I use the setdefault method to add a default value for the
operationkey in themainfunction, instreamlit_calculator.pythe terminal is my friend, and shows AssertionError
AssertionError: '=' != '\\+'I change the value of the
operationkey in thefirst_numberfunction12def first_number(operation): 13 first_number = streamlit.session_state['number'] 14 streamlit.session_state['first_number'] = first_number 15 streamlit.session_state['operation'] = operation 16 reset_number() 17 18 19def calculate():the terminal is my friend, and shows AssertionError
AssertionError: '0' != '1'I broke the test for the
'first_number'key that was passing beforeI add the value for
operationto theargslist for ther'\+'button in theadd_buttons_to_column_4function127def add_buttons_to_column_4(column_4, display): 128 column_4.button( 129 label='/', key='/', width='stretch', type='primary', 130 ) 131 column_4.button( 132 label='X', key='X', width='stretch', type='primary', 133 ) 134 column_4.button( 135 label=r'\-', key=r'\-', width='stretch', type='primary', 136 ) 137 column_4.button( 138 label=r'\+', key=r'\+', width='stretch', type='primary', 139 on_click=on_click, args=[first_number, display, r'\+'], 140 ) 141 column_4.button( 142 label='=', key='=', width='stretch', type='primary', 143 on_click=on_click, args=[calculate, display], 144 ) 145 146 147def main():the terminal is my friend, and shows AssertionError
AssertionError: '4.0120000000000005' != '-1.0'I add an assertion for the
operationkey after the-button is pressed, intest_streamlit_calculator.py151 def test_streamlit_calculator_operations(self): 152 first_number = '1' 153 second_number = '2' 154 155 operation = r'\+' 156 self.press_button(first_number) 157 # self.press_button(r'\+') 158 self.press_button(operation) 159 self.assertEqual( 160 self.tester.session_state['first_number'], 161 first_number 162 ) 163 self.assertEqual( 164 self.tester.session_state['operation'], 165 operation 166 ) 167 168 self.press_button(second_number) 169 self.press_button('=') 170 self.assertEqual( 171 self.tester.session_state['second_number'], 172 second_number 173 ) 174 175 self.assertEqual( 176 self.tester.session_state['number'], 177 str(float(first_number)+float(second_number)) 178 ) 179 180 operation = r'\-' 181 self.press_button(first_number) 182 # self.press_button(r'\-') 183 self.press_button(operation) 184 self.press_button(second_number) 185 self.press_button('=') 186 187 self.assertEqual( 188 self.tester.session_state['operation'], 189 operation 190 ) 191 192 self.assertEqual( 193 self.tester.session_state['number'], 194 str(float(first_number)-float(second_number)) 195 ) 196 197 198# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: '\\+' != '\\-'I add values for the
on_clickandargsparameters for ther'\-'button in theadd_buttons_to_column_4function instreamlit_calculator.py127def add_buttons_to_column_4(column_4, display): 128 column_4.button( 129 label='/', key='/', width='stretch', type='primary', 130 ) 131 column_4.button( 132 label='X', key='X', width='stretch', type='primary', 133 ) 134 column_4.button( 135 label=r'\-', key=r'\-', width='stretch', type='primary', 136 on_click=on_click, args=[first_number, display, r'\-'], 137 ) 138 column_4.button( 139 label=r'\+', key=r'\+', width='stretch', type='primary', 140 on_click=on_click, args=[first_number, display, r'\+'], 141 ) 142 column_4.button( 143 label='=', key='=', width='stretch', type='primary', 144 on_click=on_click, args=[calculate, display], 145 ) 146 147 148def main():the terminal is my friend, and shows AssertionError
AssertionError: '5.01' != '-1.0'alright! Time to add the calculation for subtraction
I use the operation in a dictionary in the
calculatefunction instreamlit_calculator.py19def calculate(): 20 arithmetic = { 21 r'\+': 'add', 22 } 23 24 second_number = streamlit.session_state['number'] 25 streamlit.session_state['second_number'] = second_number 26 streamlit.session_state['number'] = str( 27 float(streamlit.session_state['first_number']) 28 + float(streamlit.session_state['second_number']) 29 ) 30 31 32def plus_minus():I use the
__getattribute__method to get the add function from the calculator module19def calculate(): 20 arithmetic = { 21 r'\+': 'add', 22 } 23 24 second_number = streamlit.session_state['number'] 25 streamlit.session_state['second_number'] = second_number 26 # streamlit.session_state['number'] = str( 27 # float(streamlit.session_state['first_number']) 28 # + float(streamlit.session_state['second_number']) 29 # ) 30 operation = arithmetic[streamlit.session_state['operation']] 31 first_number = float(streamlit.session_state['first_number']) 32 second_number = float(streamlit.session_state['second_number']) 33 result = calculator.__getattribute__(operation)( 34 first_number, second_number 35 ) 36 streamlit.session_state['number'] = str(result) 37 38 39def plus_minus():the terminal is my friend, and shows AssertionError
AssertionError: '2' != '3.0'and NameError
NameError: name 'calculator' is not defined. Did you mean: 'calculate'?I add an import statement for the calculator module
1import calculator 2import streamlit 3 4 5def show_number(display):the terminal is my friend, and shows AssertionError
AssertionError: '2' != '-1.0'and KeyError
KeyError: '\\-'I add a key for
r'\-'to thearithmeticdictionary in thecalculatefunction20def calculate(): 21 arithmetic = { 22 r'\+': 'add', 23 r'\-': 'subtract', 24 } 25 26 second_number = streamlit.session_state['number'] 27 streamlit.session_state['second_number'] = second_number 28 # streamlit.session_state['number'] = str( 29 # float(streamlit.session_state['first_number']) 30 # + float(streamlit.session_state['second_number']) 31 # ) 32 operation = arithmetic[streamlit.session_state['operation']] 33 first_number = float(streamlit.session_state['first_number']) 34 second_number = float(streamlit.session_state['second_number']) 35 result = calculator.__getattribute__(operation)( 36 first_number, second_number 37 ) 38 streamlit.session_state['number'] = str(result) 39 40 41def plus_minus():the terminal is my friend, and shows AssertionError
AssertionError: '1.0099999999999998' != '-1.0'the calculation is wrong
I add an assertion for the value of the first number in the subtraction operation in test_streamlit_calculator_operations in
test_streamlit_calculator.py151 def test_streamlit_calculator_operations(self): 152 first_number = '1' 153 second_number = '2' 154 155 operation = r'\+' 156 self.press_button(first_number) 157 # self.press_button(r'\+') 158 self.press_button(operation) 159 self.assertEqual( 160 self.tester.session_state['first_number'], 161 first_number 162 ) 163 self.assertEqual( 164 self.tester.session_state['operation'], 165 operation 166 ) 167 168 self.press_button(second_number) 169 self.press_button('=') 170 self.assertEqual( 171 self.tester.session_state['second_number'], 172 second_number 173 ) 174 175 self.assertEqual( 176 self.tester.session_state['number'], 177 str(float(first_number)+float(second_number)) 178 ) 179 180 operation = r'\-' 181 self.press_button(first_number) 182 # self.press_button(r'\-') 183 self.press_button(operation) 184 self.assertEqual( 185 self.tester.session_state['first_number'], 186 first_number 187 ) 188 189 self.press_button(second_number) 190 self.press_button('=') 191 192 self.assertEqual( 193 self.tester.session_state['operation'], 194 operation 195 ) 196 197 self.assertEqual( 198 self.tester.session_state['number'], 199 str(float(first_number)-float(second_number)) 200 ) 201 202 203# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: '3.01' != '1'ah! the calculation is correct, I have to reset the calculator after it shows the result
I reset the calculator after the first operation function
151def test_streamlit_calculator_operations(self): 152 first_number = '1' 153 second_number = '2' 154 155 operation = r'\+' 156 self.press_button(first_number) 157 # self.press_button(r'\+') 158 self.press_button(operation) 159 self.assertEqual( 160 self.tester.session_state['first_number'], 161 first_number 162 ) 163 self.assertEqual( 164 self.tester.session_state['operation'], 165 operation 166 ) 167 168 self.press_button(second_number) 169 self.press_button('=') 170 self.assertEqual( 171 self.tester.session_state['second_number'], 172 second_number 173 ) 174 175 self.assertEqual( 176 self.tester.session_state['number'], 177 str(float(first_number)+float(second_number)) 178 ) 179 180 self.press_button('AC') 181 182 operation = r'\-' 183 self.press_button(first_number) 184 # self.press_button(r'\-') 185 self.press_button(operation) 186 self.assertEqual( 187 self.tester.session_state['first_number'], 188 first_number 189 ) 190 191 self.press_button(second_number) 192 self.press_button('=') 193 194 self.assertEqual( 195 self.tester.session_state['operation'], 196 operation 197 ) 198 199 self.assertEqual( 200 self.tester.session_state['number'], 201 str(float(first_number)-float(second_number)) 202 )the test passes. Wow!
I remove the commented lines
151 def test_streamlit_calculator_operations(self): 152 first_number = '1' 153 second_number = '2' 154 155 operation = r'\+' 156 self.press_button(first_number) 157 self.press_button(operation) 158 self.assertEqual( 159 self.tester.session_state['first_number'], 160 first_number 161 ) 162 self.assertEqual( 163 self.tester.session_state['operation'], 164 operation 165 ) 166 167 self.press_button(second_number) 168 self.press_button('=') 169 self.assertEqual( 170 self.tester.session_state['second_number'], 171 second_number 172 ) 173 174 self.assertEqual( 175 self.tester.session_state['number'], 176 str(float(first_number)+float(second_number)) 177 ) 178 179 self.press_button('AC') 180 181 operation = r'\-' 182 self.press_button(first_number) 183 self.press_button(operation) 184 self.assertEqual( 185 self.tester.session_state['first_number'], 186 first_number 187 ) 188 189 self.press_button(second_number) 190 self.press_button('=') 191 192 self.assertEqual( 193 self.tester.session_state['operation'], 194 operation 195 ) 196 197 self.assertEqual( 198 self.tester.session_state['number'], 199 str(float(first_number)-float(second_number)) 200 )this test is long and will get longer when I add the other two operations
I make a dictionary for the operations in test_streamlit_calculator_operations
151def test_streamlit_calculator_operations(self): 152 arithmetic_operations = { 153 r'\+': 'add', 154 r'\-': 'subtract', 155 'X': 'multiply', 156 '/': 'divide' 157 } 158 159 first_number = '1' 160 second_number = '2'I use a for loop with the dictionary
151 def test_streamlit_calculator_operations(self): 152 arithmetic_operations = { 153 r'\+': 'add', 154 r'\-': 'subtract', 155 'X': 'multiply', 156 '/': 'divide' 157 } 158 159 first_number = '1' 160 second_number = '2' 161 162 for operation in arithmetic_operations: 163 with self.subTest(operation=operation): 164 self.press_button(first_number) 165 self.press_button(operation) 166 167 self.assertEqual( 168 self.tester.session_state['first_number'], 169 'BOOM!!!' 170 ) 171 172 operation = r'\+'the terminal is my friend, and shows AssertionError
SUBFAILED(operation='\\+') ... - AssertionError: '1' != 'BOOM!!!' SUBFAILED(operation='\\-') ... - AssertionError: '1' != 'BOOM!!!' SUBFAILED(operation='X') ... - AssertionError: '1' != 'BOOM!!!' SUBFAILED(operation='/') ... - AssertionError: '1' != 'BOOM!!!' FAILED ... - AssertionError: '111' != '1'I change the expectation
166 self.assertEqual( 167 self.tester.session_state['first_number'], 168 first_number 169 )the terminal is my friend, and shows AssertionError
AssertionError: '111' != '1'I reset the calculator with the
ACbutton167 self.assertEqual( 168 self.tester.session_state['first_number'], 169 first_number 170 ) 171 172 self.press_button('AC')the test passes
I add an assertion for the operation
151 def test_streamlit_calculator_operations(self): 152 arithmetic_operations = { 153 r'\+': 'add', 154 r'\-': 'subtract', 155 'X': 'multiply', 156 '/': 'divide' 157 } 158 159 first_number = '1' 160 second_number = '2' 161 162 for operation in arithmetic_operations: 163 with self.subTest(operation=operation): 164 self.press_button(first_number) 165 self.press_button(operation) 166 167 self.assertEqual( 168 self.tester.session_state['first_number'], 169 first_number 170 ) 171 172 self.assertEqual( 173 self.tester.session_state['operation'], 174 'BOOM!!!' 175 ) 176 177 self.press_button('AC') 178 179 operation = r'\+'the terminal is my friend, and shows AssertionError
SUBFAILED(operation='\\+') ... - AssertionError: '\\+' != 'BOOM!!!' SUBFAILED(operation='\\-') ... - AssertionError: '\\-' != 'BOOM!!!' SUBFAILED(operation='X') ... - AssertionError: '\\-' != 'BOOM!!!' SUBFAILED(operation='/') ... - AssertionError: '\\-' != 'BOOM!!!' FAILED ... - AssertionError: '111' != '1'I change the expectation
171 self.assertEqual( 172 self.tester.session_state['operation'], 173 operation 174 )the terminal is my friend, and shows AssertionError
SUBFAILED(operation='X') ... - AssertionError: '\\-' != 'X' SUBFAILED(operation='/') ... - AssertionError: '\\-' != '/' FAILED ... - AssertionError: '111' != '1'I add values for the
on_clickandargsparameters to theXbutton in theadd_buttons_to_column_4function instreamlit_calculator.py140def add_buttons_to_column_4(column_4, display): 141 column_4.button( 142 label='/', key='/', width='stretch', type='primary', 143 ) 144 column_4.button( 145 label='X', key='X', width='stretch', type='primary', 146 on_click=on_click, args=[first_number, display, 'X'], 147 ) 148 column_4.button( 149 label=r'\-', key=r'\-', width='stretch', type='primary', 150 on_click=on_click, args=[first_number, display, r'\-'], 151 ) 152 column_4.button( 153 label=r'\+', key=r'\+', width='stretch', type='primary', 154 on_click=on_click, args=[first_number, display, r'\+'], 155 ) 156 column_4.button( 157 label='=', key='=', width='stretch', type='primary', 158 on_click=on_click, args=[calculate, display], 159 )the terminal is my friend, and shows AssertionError
SUBFAILED(operation='/') ... - AssertionError: 'X' != '/' FAILED ... - AssertionError: '11' != '1'I add values for the
on_clickandargsparameters to the/button in theadd_buttons_to_column_4functiondef add_buttons_to_column_4(column_4, display): column_4.button( label='/', key='/', width='stretch', type='primary', on_click=on_click, args=[first_number, display, '/'], ) column_4.button( label='X', key='X', width='stretch', type='primary', on_click=on_click, args=[first_number, display, 'X'], ) column_4.button( label=r'\-', key=r'\-', width='stretch', type='primary', on_click=on_click, args=[first_number, display, r'\-'], ) column_4.button( label=r'\+', key=r'\+', width='stretch', type='primary', on_click=on_click, args=[first_number, display, r'\+'], ) column_4.button( label='=', key='=', width='stretch', type='primary', on_click=on_click, args=[calculate, display], ) def main():the test passes
I add a button press for the second number and
=, then an assertion intest_streamlit_calculator.py151 def test_streamlit_calculator_operations(self): 152 arithmetic_operations = { 153 r'\+': 'add', 154 r'\-': 'subtract', 155 'X': 'multiply', 156 '/': 'divide' 157 } 158 159 first_number = '1' 160 second_number = '2' 161 162 for operation in arithmetic_operations: 163 with self.subTest(operation=operation): 164 self.press_button(first_number) 165 self.press_button(operation) 166 self.assertEqual( 167 self.tester.session_state['first_number'], 168 first_number 169 ) 170 171 self.assertEqual( 172 self.tester.session_state['operation'], 173 operation 174 ) 175 176 self.press_button(second_number) 177 self.press_button('=') 178 179 self.assertEqual( 180 self.tester.session_state['second_number'], 181 'BOOM!!!' 182 ) 183 184 self.press_button('AC')the terminal is my friend, and shows AssertionError
SUBFAILED(operation='\\+') ... - AssertionError: '2' != 'BOOM!!!' SUBFAILED(operation='\\-') ... - AssertionError: '3.01' != '1' SUBFAILED(operation='X') ... - AssertionError: '2' != 'BOOM!!!'and KeyError
SUBFAILED(operation='/') t... - KeyError: '1' FAILED ... - KeyError: '1'I change the expectation
179 self.assertEqual( 180 self.tester.session_state['second_number'], 181 second_number 182 )the terminal still shows AssertionError and KeyError
I add
Xto thearithmeticdictionary in thecalculatefunction instreamlit_calculator.py20def calculate(): 21 arithmetic = { 22 r'\+': 'add', 23 r'\-': 'subtract', 24 'X': 'multiply', 25 }the terminal is my friend, and shows KeyError
SUBFAILED(operation='/') ... - KeyError: 'AC' FAILED ... - KeyError: '1'I add
/to thearithmeticdictionary20def calculate(): 21 arithmetic = { 22 r'\+': 'add', 23 r'\-': 'subtract', 24 'X': 'multiply', 25 '/': 'divide', 26 } 27 28 second_number = streamlit.session_state['number'] 29 streamlit.session_state['second_number'] = second_number 30 # streamlit.session_state['number'] = str( 31 # float(streamlit.session_state['first_number']) 32 # + float(streamlit.session_state['second_number']) 33 # ) 34 operation = arithmetic[streamlit.session_state['operation']] 35 first_number = float(streamlit.session_state['first_number']) 36 second_number = float(streamlit.session_state['second_number']) 37 result = calculator.__getattribute__(operation)( 38 first_number, second_number 39 ) 40 streamlit.session_state['number'] = str(result) 41 42 43def plus_minus():the test passes
I add an assertion for the calculation in
test_streamlit_calculator.py151 def test_streamlit_calculator_operations(self): 152 arithmetic_operations = { 153 r'\+': 'add', 154 r'\-': 'subtract', 155 'X': 'multiply', 156 '/': 'divide' 157 } 158 159 first_number = '1' 160 second_number = '2' 161 162 for operation in arithmetic_operations: 163 with self.subTest(operation=operation): 164 self.press_button(first_number) 165 self.press_button(operation) 166 self.assertEqual( 167 self.tester.session_state['first_number'], 168 first_number 169 ) 170 171 self.assertEqual( 172 self.tester.session_state['operation'], 173 operation 174 ) 175 176 self.press_button(second_number) 177 self.press_button('=') 178 179 self.assertEqual( 180 self.tester.session_state['second_number'], 181 second_number 182 ) 183 184 function = arithmetic_operations[operation] 185 self.assertEqual( 186 self.tester.session_state['number'], 187 src.calculator.__getattribute__(function)( 188 first_number, second_number 189 ) 190 ) 191 192 self.press_button('AC')the terminal is my friend, and shows Exceptions
I add an import statement at the top of
test_streamlit_calculator.pythe terminal is my friend, and shows AssertionError
SUBFAILED(operation='\\+') ... - AssertionError: '3.0' != 'brmph?! Numbers only. Try again...' SUBFAILED(operation='\\-') ... - AssertionError: '3.01' != '1' SUBFAILED(operation='X') ... - AssertionError: '2.0' != 'brmph?! Numbers only. Try again...' SUBFAILED(operation='/') ... - AssertionError: '2.01' != '1'I change
first_numberandsecond_numberto floats in the calculation intest_streamlit_calculator.py185 function = arithmetic_operations[operation] 186 self.assertEqual( 187 self.tester.session_state['number'], 188 src.calculator.__getattribute__(function)( 189 float(first_number), 190 float(second_number) 191 ) 192 )the terminal is my friend, and shows AssertionError
SUBFAILED(operation='\\+') ... - AssertionError: '3.0' != 3.0 SUBFAILED(operation='\\-') ... - AssertionError: '3.01' != '1' SUBFAILED(operation='X') ... - AssertionError: '2.0' != 2.0 SUBFAILED(operation='/') ... - AssertionError: '2.01' != '1'I change the expectation to a string
function = arithmetic_operations[operation] self.assertEqual( self.tester.session_state['number'], str( src.calculator.__getattribute__(function)( float(first_number), float(second_number) ) ) )the test passes
I remove the other statements from test_streamlit_calculator_operations
151 def test_streamlit_calculator_operations(self): 152 arithmetic_operations = { 153 r'\+': 'add', 154 r'\-': 'subtract', 155 'X': 'multiply', 156 '/': 'divide' 157 } 158 159 first_number = '1' 160 second_number = '2' 161 162 for operation in arithmetic_operations: 163 with self.subTest(operation=operation): 164 self.press_button(first_number) 165 self.press_button(operation) 166 167 self.assertEqual( 168 self.tester.session_state['first_number'], 169 first_number 170 ) 171 172 self.assertEqual( 173 self.tester.session_state['operation'], 174 operation 175 ) 176 177 self.press_button(second_number) 178 self.press_button('=') 179 180 self.assertEqual( 181 self.tester.session_state['second_number'], 182 second_number 183 ) 184 185 function = arithmetic_operations[operation] 186 self.assertEqual( 187 self.tester.session_state['number'], 188 str( 189 src.calculator.__getattribute__(function)( 190 float(first_number), 191 float(second_number) 192 ) 193 ) 194 ) 195 196 self.press_button('AC') 197 198 199# Exceptions seenI remove the commented lines from the
calculatefunction instreamlit_calculator.py20def calculate(): 21 arithmetic = { 22 r'\+': 'add', 23 r'\-': 'subtract', 24 'X': 'multiply', 25 '/': 'divide', 26 } 27 28 second_number = streamlit.session_state['number'] 29 streamlit.session_state['second_number'] = second_number 30 31 operation = arithmetic[streamlit.session_state['operation']] 32 first_number = float(streamlit.session_state['first_number']) 33 second_number = float(streamlit.session_state['second_number']) 34 35 result = calculator.__getattribute__(operation)( 36 first_number, second_number 37 ) 38 39 streamlit.session_state['number'] = str(result) 40 41 42def plus_minus():
All tests are green and when I try calculations, they work… there is a problem - When I press a new number after I get the result, If I do not press C or AC to reset the number, it adds the number to the end of the result instead of replacing it
close the project
I close
test_streamlit_calculator.py,streamlit_calculator.pyin the editorI click in the first terminal, then use q on the keyboard to leave the tests. The terminal goes back to the command line
I change directory to the parent of
calculatorcd ..the terminal is my friend, and shows
.../pumping_pythonI am back in the
pumping_pythondirectoryI click in the second terminal, then use ctrl+c on the keyboard to close the web server. The terminal goes back to the command line
I change directory to the parent of
calculatorcd ..the terminal is my friend, and shows
.../pumping_pythonI am back in the
pumping_pythondirectory
review
I made a website using Streamlit with a
I used while loops and added tests for
code from the chapter
what is next?
You have completed an amazing journey from pure functions to a real web application! Celebrate it
You now know how to:
Build a program with Test-Driven Development
Turn it into a Flask_ website
Turn it into a Streamlit app
Would you like to see how to make a Calculator with a Large Language Model?
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