how to make a calculator 10
I have a solid calculator from chapters 1–8 and a Flask website from chapter 9. Now I want to make it beautiful and ridiculously easy to use by turning it into a modern web app with Streamlit.
Streamlit lets me build professional-looking web apps with almost no extra code.
preview
These are the tests I have by the end of the chapter
1import unittest
2
3import streamlit as st
4
5
6class TestCalculatorStreamlit(unittest.TestCase):
7
8 def test_streamlit_can_be_imported(self):
9 """Make sure Streamlit is installed and importable"""
10 self.assertIsNotNone(st)
11
12 def test_streamlit_calculator_app(self):
13 """The main function in our Streamlit app exists and is callable"""
14 from src.streamlit_app import main
15 self.assertTrue(callable(main))
16
17 def test_calculator_error_messages_still_work(self):
18 """Our original calculator error messages are still working"""
19 import src.calculator
20
21 self.assertEqual(
22 src.calculator.divide(10, 0),
23 'brmph?! I cannot divide by 0. Try again...'
24 )
25
26 self.assertEqual(
27 src.calculator.add(None, 5),
28 'brmph?! Numbers only. Try again...'
29 )
30
31 self.assertEqual(
32 src.calculator.multiply('hello', 3),
33 'brmph?! Numbers only. Try again...'
34 )
35
36
37# Exceptions seen
38# (none new - we reuse everything from chapters 1-8)
open the project
I change directory to the
calculatorfoldercd calculatorI install Streamlit
uv add streamlitI use
pytest-watcherto run the testsuv run pytest-watcher . --nowI create a new test file for the Streamlit version
touch tests/test_calculator_streamlit.py
test_streamlit_installed
RED: make it fail
I open tests/test_calculator_streamlit.py and write
1import unittest
2import streamlit as st
3
4class TestCalculatorStreamlit(unittest.TestCase):
5
6 def test_streamlit_can_be_imported(self):
7 self.assertIsNotNone(st)
the terminal shows ModuleNotFoundError
GREEN: make it pass
I already ran uv add streamlit, so the test passes.
test_streamlit_calculator_app
RED: make it fail
I add a test for the app itself
8 def test_streamlit_can_be_imported(self):
9 ...
10
11 def test_streamlit_calculator_app(self):
12 from src.streamlit_app import main
13 self.assertTrue(callable(main))
the terminal shows ModuleNotFoundError
GREEN: make it pass
I create the file
touch src/streamlit_app.pyI open it and add the basic app
1import streamlit as st 2import src.calculator as calc 3 4def main(): 5 st.title("🧮 My TDD Calculator") 6 st.write("Built step-by-step with Test-Driven Development!") 7 8if __name__ == "__main__": 9 main()
The test passes.
REFACTOR: make it better
Now I build the full interactive calculator.
I update the test to also check core behavior (we already know it works from earlier chapters, but it feels good to see it here).
I expand src/streamlit_app.py:
6 def main():
7 st.title("🧮 My TDD Calculator")
8 st.write("Built step-by-step with Test-Driven Development!")
9
10 col1, col2, col3 = st.columns([3, 1, 3])
11
12 with col1:
13 first = st.number_input("First number", value=0.0, step=0.1)
14
15 with col2:
16 operation = st.selectbox(
17 "Operation",
18 ["+", "-", "×", "÷"]
19 )
20
21 with col3:
22 second = st.number_input("Second number", value=0.0, step=0.1)
23
24 if st.button("Calculate", type="primary"):
25 try:
26 ops = {
27 "+": calc.add,
28 "-": calc.subtract,
29 "×": calc.multiply,
30 "÷": calc.divide
31 }
32 result = ops[operation](first, second)
33 st.success(f"**Result:** {result}")
34
35 except ZeroDivisionError:
36 st.error("brmph?! I cannot divide by 0. Try again...")
37 except Exception:
38 st.error("brmph?! Numbers only. Try again...")
39
40 # Bonus: show history
41 if "history" not in st.session_state:
42 st.session_state.history = []
43
44 if st.button("Clear history"):
45 st.session_state.history = []
46
47 if st.session_state.history:
48 st.subheader("Recent calculations")
49 for item in reversed(st.session_state.history[-5:]):
50 st.write(item)
51
52 if __name__ == "__main__":
53 main()
The app now looks and works beautifully.
test_error_messages_in_streamlit
RED: make it fail
I add
20 def test_error_messages_in_streamlit(self):
21 self.assertEqual(
22 src.calculator.divide(10, 0),
23 'brmph?! I cannot divide by 0. Try again...'
24 )
the test already passes (thanks to chapters 3–4).
how to run the app
In the terminal I type:
uv run streamlit run src/streamlit_app.py
A browser window opens automatically with my beautiful calculator!
close the project
I close all files
I click in the terminal, then use q to leave the tests
I change directory to the parent
cd ..
review
I now have three different versions of the same calculator:
Pure Python (chapters 1–8)
Flask website (chapter 9)
Streamlit web app (chapter 10) — the fastest and most beautiful version
The core calculator code never changed. All my tests still protect it. This is the real power of Test-Driven Development.
code from the chapter
what is next?
You have completed an amazing journey from pure functions to real web applications!
You now know how to:
Build programs with Test-Driven Development
Turn them into Flask websites
Turn them into beautiful Streamlit apps
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