Warming up the neural circuits...
By the end of this chapter you will:
If your program fails and you cannot answer "what happened" quickly, you are guessing.
Logging and tests are the two smallest habits that create the biggest jump in professional code quality.
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s - %(message)s",
)
logger = logging.getLogger("expenses")
def add_expense(amount: float, category: str) -> None:
logger.info("adding expense", extra={"amount": amount, "category": category})
if amount <= 0:
logger.warning("invalid amount", extra={"amount": amount})
raise ValueError("amount must be positive")Use print for temporary exploration, logging for durable diagnostics.
# app/math_utils.py
def total(values: list[float]) -> float:
return sum(values)
# tests/test_math_utils.py
from app.math_utils import total
def test_total_handles_empty_list():
assert total
from pathlib import Path
def parse_amount(text: str) -> float:
value = float(text)
if value <= 0:
raise ValueError("amount must be positive")
README includes run, test, and troubleshooting steps.| Mistake | Why it hurts | Better move |
|---|---|---|
Only using print for diagnostics | No level/severity discipline | Use structured logging levels |
| Tests coupled to real filesystem paths | Flaky test behavior | Use tmp paths and isolated fixtures |
| Huge integration test only | Slow feedback loop | Add focused unit tests first |
| Untestable functions with side effects | Hard to isolate bugs | Separate pure logic from I/O |
A function that validates amount and saves an expenseMeaningful log lines for success, validation failure, and unexpected exceptionsparse_amount("10.5") and parse_amount("-2")Positive value passes, negative value raises ValueErrorA function that writes JSON records to diskTest verifies file creation and content without touching real project dataFunction mixes printing, parsing, and calculationsPure helper function + thin I/O wrapperProject with passing tests but missing lint checkChecklist fails until all quality gates passBeginner:
"When should you use logging instead of print?"
Use logging for durable diagnostics with severity levels and context. Use print only for quick temporary checks.
"Why are small unit tests important?"
They provide fast feedback and isolate regressions, making refactors safer.
Senior:
"How do you design code for testability from day one?"
Separate pure logic from I/O, inject dependencies, and avoid hidden global state.
"What is your minimum CI quality gate for a Python CLI app?"
At minimum: unit tests, lint, formatting checks, and failure visibility through logs.
Need runtime visibility? -> logging
Need safe refactors? -> pytest
Need easier testing? -> separate logic from I/O
Need project readiness? -> pass checklist before shippingWhat is the main advantage of logging over print statements?
Run with pytest -q and keep tests deterministic.