We created a Python Flask expense tracker application with comprehensive testing - and in the process, we encountered and fixed several real bugs! This gave us a great opportunity to observe debugging in action.
Problem: When deleting or clearing expenses, we were reassigning the global expenses list instead of modifying it in place.
# ❌ WRONG - Creates new list, tests still reference old one
expenses = [e for e in expenses if e.id != expense_id]
# ✅ CORRECT - Modifies existing list in place
expenses[:] = [e for e in expenses if e.id != expense_id]Why it mattered: Tests were failing because they held references to the original list, which wasn't being updated.
Debugging approach used:
- Read the error messages carefully
- Examined test output to see what was expected vs. actual
- Traced through the code logic
- Fixed by changing reassignment to in-place modification
Problem: Tests were looking for "Food & Dining" but Flask was outputting "Food & Dining" (HTML-encoded).
# ❌ WRONG - Looking for raw ampersand
self.assertIn(b'Food & Dining', response.data)
# ✅ CORRECT - Account for HTML encoding
html_encoded = category.replace('&', '&')
self.assertIn(html_encoded.encode(), response.data)Why it mattered: String matching failed because we weren't accounting for how web frameworks encode special characters.
Debugging approach used:
- Examined the actual HTML response
- Understood that
&becomes&in HTML - Updated tests to match reality
-
Systematic testing approach
- We built tests WHILE building the app
- Tests immediately caught bugs before users would see them
- Had comprehensive coverage (23 tests covering unit, integration, API)
-
Clear error messages
- Our test assertions were specific
- Made it easy to identify exactly what was failing
-
Iterative problem solving
- First run: Found 8 failing tests
- Analyzed the failures
- Fixed root causes
- Re-ran tests: All passed ✅
Based on this exercise, here are some areas where debugging skills can improve:
-
Read Error Messages Completely
- Don't just look at "FAIL" - read the full traceback
- The assertion errors told us exactly what we were getting vs. expecting
- Example:
AssertionError: b'Coffee' not foundimmediately told us the data wasn't there
-
Understand State Management
- The global state bug is a classic Python gotcha
- When working with mutable data structures, understand the difference between:
- Creating a new object:
expenses = [] - Modifying in place:
expenses[:] = []orexpenses.clear()
- Creating a new object:
-
Test Early, Test Often
- We caught bugs immediately because we ran tests right after writing code
- Waiting until the end would have made debugging harder
- Each passing test gives confidence; each failure gives feedback
-
Use Debugging Tools
- For more complex bugs, consider:
print()statements to trace execution- Python debugger (
pdb) - Logging instead of prints for production code
- Flask debug mode (already enabled in our app)
- For more complex bugs, consider:
-
Reproduce Bugs Consistently
- All our bugs were caught by automated tests
- This means we can reproduce them anytime
- Manual testing alone would have missed these edge cases
Here's the systematic approach we used:
1. Write code
↓
2. Run tests immediately
↓
3. Tests fail → Read error messages carefully
↓
4. Form hypothesis about the problem
↓
5. Examine relevant code section
↓
6. Make targeted fix
↓
7. Re-run tests
↓
8. All pass? ✅ Move on | Still failing? → Back to step 3
Bad approach:
- Click around randomly
- Change random parts of the code
- Hope it starts working
Good approach (what we did):
- Write a test that reproduces the issue
- Run the test - it fails
- Look at the error message
- Examine the delete function
- Notice we're reassigning instead of mutating
- Fix it
- Test passes!
- Tests are your safety net - They catch bugs you didn't know existed
- Global state is tricky - Be careful with mutable global variables
- HTML encoding matters - When testing web apps, remember special characters get encoded
- Fix root causes, not symptoms - We fixed the mutation issue, not just the test
- Debugging is a skill - Gets better with practice and systematic approaches
- ✅ Read the full error message
- ✅ Reproduce the bug reliably
- ✅ Change one thing at a time
- ✅ Verify your fix with tests
- ✅ Understand WHY it was broken
- ❌ Don't guess randomly
- ❌ Don't skip writing tests
- ❌ Don't ignore warnings
Based on what we did together, rate yourself on these areas:
- Do you write tests as you code? ⭐⭐⭐⭐⭐
- Do you have unit + integration tests? ⭐⭐⭐⭐⭐
- Do you read error messages completely? _____
- Do you understand the root cause before fixing? _____
- Do you verify fixes with tests? _____
- Do you use a debugger (not just print)? _____
- Do you understand stack traces? _____
- Do you write reproducible test cases? _____
-
Practice with Real Bugs
- Try breaking this application intentionally
- Write tests that catch the breaks
- Fix them systematically
-
Learn Python Debugging Tools
- Try
pdb(Python debugger) - Use IDE debugging features
- Learn to read stack traces fluently
- Try
-
Study Common Bug Patterns
- Off-by-one errors
- Null/None references
- Type mismatches
- State management issues
-
Build More Projects
- Each project teaches you new debugging scenarios
- Keep using test-driven development
- Document bugs you find for future reference
Overall Assessment: Strong Foundation 🌟
You demonstrated excellent debugging practices by:
- Writing comprehensive tests
- Running them early and often
- Reading error messages
- Making targeted fixes
- Verifying solutions
Areas to focus on:
- Understanding state management and mutation
- Knowing when to use debugging tools beyond tests
- Building intuition for common bug patterns
Keep building, keep testing, keep debugging! 🚀