Manual Setup: Tutorial 12 — Debugging with 5 Whys
If you'd rather run commands by hand instead of using setup.py, follow these steps. Run them from a directory where you want the tutorial project created (e.g. ~/projects or cd $(mktemp -d)).
1. Create the project skeleton
mkdir -p csv-processor/src csv-processor/tests
cd csv-processor
2. Create src/processor.py
The processor has an intentional bug you will investigate in Step 4 of the tutorial.
# src/processor.py
# This file has TWO intentional bugs you will discover during the tutorial.
# Don't fix them by reading -- let /nw-root-why walk you through the investigation.
def process_csv(input_text: str) -> list[dict]:
lines = input_text.strip().split("\n")
header = lines[0].split(",")
results = []
for line in lines[1:-1]:
parts = line.split(",")
if len(parts) != len(header):
continue
row = dict(zip(header, parts))
if not row.get("amount"):
continue
row["amount"] = float(row["amount"])
results.append(row)
return results
def summarize(rows: list[dict]) -> dict:
return {
"count": len(rows),
"total": round(sum(r["amount"] for r in rows), 2),
}
3. Create tests/sample.csv
cat > tests/sample.csv << 'EOF'
name,amount,category
Alice,50.00,food
Bob,30.00,transport
Carol,20.00,food
Dave,,transport
Eve,15.00,office
Frank,10.00,"food, drinks"
Grace,25.00,transport
Heidi,40.00,food
EOF
4. Create tests/test_processor.py
# tests/test_processor.py
import pytest
from src.processor import process_csv, summarize
SAMPLE_CSV = open("tests/sample.csv").read()
def test_process_csv_row_count():
"""8 rows in CSV, 1 has empty amount, so 7 should remain."""
rows = process_csv(SAMPLE_CSV)
assert len(rows) == 7, f"Expected 7 rows, got {len(rows)}: {[r['name'] for r in rows]}"
def test_process_csv_skips_empty_amount():
rows = process_csv(SAMPLE_CSV)
names = [r["name"] for r in rows]
assert "Dave" not in names, "Dave has empty amount and should be skipped"
def test_summarize_total():
rows = process_csv(SAMPLE_CSV)
result = summarize(rows)
assert result["total"] == 190.00, f"Expected 190.00, got {result['total']}"
def test_summarize_count():
rows = process_csv(SAMPLE_CSV)
result = summarize(rows)
assert result["count"] == 7
5. Create conftest.py
So pytest can find the src module:
echo 'import sys; sys.path.insert(0, ".")' > conftest.py
6. Create the virtualenv and install pytest
python3 -m venv .venv
source .venv/bin/activate
pip install pytest
Windows users: Replace
source .venv/bin/activatewith.venv\Scripts\activate.
7. Initialize git with the buggy starting state
echo ".venv/" > .gitignore
echo "__pycache__/" >> .gitignore
git init
git add -A
git commit -m "feat: csv processor with intentional bug (starting point for debugging)"
Verify
Run the tests — 3 should fail. That's expected: the failing tests are the bug you will investigate during the tutorial.
pytest tests/ -v --no-header
You should now have:
- A
csv-processor/directory withsrc/processor.py,tests/sample.csv,tests/test_processor.py,conftest.py,.gitignore - A
.venv/virtual environment withpytestinstalled - A clean git repository with one initial commit
- A failing test suite (3 failures) — exactly the symptom you will trace during the tutorial
You're ready to start the debugging tutorial.