1 The Problem
We want a budget tracker: record income and expenses (each with an amount and category), then show the total balance and a breakdown of spending per category. It teaches recording transactions and summarising them — totalling and grouping, the core of any reporting tool.
2 How to Think About It
Think about recording and summarising, before any code:
3 The Build — explained part by part
Here is the complete budget tracker. Each part is explained below.
import json
import os
from collections import defaultdict
FILE = "budget.json"
def load():
if os.path.exists(FILE):
with open(FILE) as f:
return json.load(f)
return []
def save(entries):
with open(FILE, "w") as f:
json.dump(entries, f)
entries = load()
while True:
print("\n1. Add 2. Balance 3. By category 4. Quit")
choice = input("Choose: ")
if choice == "1":
amount = float(input("Amount (negative for expense): "))
category = input("Category: ")
entries.append({"amount": amount, "category": category})
save(entries)
elif choice == "2":
balance = sum(e["amount"] for e in entries)
print(f"Balance: {balance:.2f}")
elif choice == "3":
totals = defaultdict(float)
for e in entries:
totals[e["category"]] += e["amount"]
for category, total in totals.items():
print(f"{category}: {total:.2f}")
elif choice == "4":
break
sum(e["amount"] for e in entries) — the balance: add up every amount in one line. Income adds, expenses (being negative) subtract.
from collections import defaultdict — a dictionary that starts each new key at zero automatically, so we can add to a category without first checking it exists.
totals[e["category"]] += e["amount"] — the grouping: for each entry, add its amount to its category’s running total. After the loop,
totals holds the sum per category.{balance:.2f} — format as money with two decimals.
if category in totals before adding — works, but defaultdict removes the need.defaultdict(float) so new categories start at 0 automatically.:.2f — money shows with messy decimals.4 Test & Prove Each Part
We test the two summaries — total balance and per-category grouping — with known entries.
from collections import defaultdict
def balance(entries):
return sum(e["amount"] for e in entries)
def by_category(entries):
totals = defaultdict(float)
for e in entries:
totals[e["category"]] += e["amount"]
return dict(totals)
def test_balance():
"""Balance sums all amounts."""
entries = [{"amount": 100, "category": "pay"}, {"amount": -30, "category": "food"}]
assert balance(entries) == 70
def test_expenses_reduce():
"""Negative amounts reduce the balance."""
assert balance([{"amount": -50, "category": "rent"}]) == -50
def test_grouping():
"""Amounts group and total by category."""
entries = [{"amount": -10, "category": "food"}, {"amount": -5, "category": "food"}]
assert by_category(entries)["food"] == -15
Run with pytest -v. The balance and by_category functions hold the summarising logic, tested against entries where we know the totals. This is exactly how real reporting code is tested.
5 The Interface
What it expects
Amount: -25
Category: groceriesWhat it returns
Balance: 475.00
groceries: -125.00
salary: 600.006 Run It & Automate It
python3 budget.pyAdd income and expenses, then check your balance and category breakdown.
Jenkins runs the tests automatically — each line explained below.
Choose: 2
Balance: 475.00
Choose: 3
salary: 600.00
groceries: -125.00KeyError in the category breakdown.defaultdict(float) instead of a plain dict so missing keys default to 0.// Jenkinsfile — automated tests on every change.
pipeline {
agent any
stages {
stage('Get the code') { steps { checkout scm } } // download the code
stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } } // test tool
stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } } // run every test
}
post {
success { echo 'All tests passed.' }
failure { echo 'A test failed — look above.' }
}
}
- Monthly view. Add a date and total by month. (Teaches: dates and grouping.)
- Budget limits. Warn when a category exceeds a set limit. (Teaches: comparisons on totals.)
- Biggest expense. Find the single largest spend. (Teaches: max with a key.)
sum() and a per-group breakdown with defaultdict. Totalling and grouping is the engine behind every dashboard and report. Related: collections, Comprehensions.