Day 19 Β· The crime-scene kit

Testing II & Debugging

You will be able to
  • Use fixtures to share setup, and tmp_path to test file-writing code safely
  • Replace an environment dependency in a test with monkeypatch, and say when mocking is a smell
  • Read a traceback bottom-up and locate the true failing frame
  • Drive pdb via breakpoint(): inspect variables, step, and continue
  • Convert any fixed bug into a permanent regression test
Today's ~120 minutes
Spaced-rep warm-up: due flashcards10 min
ELI5 + tech read: fixtures, tracebacks, pdb, regression tests20 min
Guided: tmp_path/monkeypatch tests + the pdb tour35 min
Practice: bisect the mystery20 min
Project: harden the analyzer suite to 15+ tests25 min
Quiz + flashcards10 min

Builds on: Day 18 β€” pytest fundamentals Β· Day 5 β€” Files & exceptions

The analogy

When a detective arrives at a crime scene, the amateurs are already trampling it β€” moving furniture, guessing suspects, changing things at random. The professional does something slower and far faster: cordon off the scene, photograph everything exactly as found, dust for prints, and only THEN form a theory. Debugging is the same discipline. The traceback is the photograph β€” a complete record of where the program died and every call that led there; you read it before touching anything. The debugger (pdb) is the fingerprint kit β€” it freezes the program mid-crime so you can inspect every variable as it actually was, not as you assume it was. Bisection is narrowing the suspect list: cut the possibilities in half, test, repeat.

And today's testing tools are the forensics lab itself: fixtures are prepared, sterile workbenches every analysis starts from β€” same setup, every time, torn down after. tmp_path hands each test a disposable room so file experiments can't contaminate the real house. When the case closes, one rule separates professionals from amateurs: every solved crime becomes a regression test β€” an alarm rigged so this exact crime can never be committed silently again.

Why this matters on the job

Interview reality: you will spend far more professional hours debugging than writing fresh code, and LLM systems make it harder β€” failures are often buried in glue code, not the model. The discipline transfers directly: reading a traceback calmly is the same skill as reading a failed trace on Day 142; bisection is how you find which prompt change broke an eval on Day 141; "reproduce, then fix, then rig the alarm" is exactly the log-to-eval-case flywheel of Day 143. FDEs debug in customer environments with limited access (Day 172) β€” method, not luck, is all you get to bring.

Guided practice

guided 1

Fixtures + tmp_path: testing the I/O shell

20 min
  1. In tests/test_io.py, write the sample_log fixture from the starter code, then tests for your analyzer's file-facing functions: reading a log file, and the missing-file behavior.
  2. Terminal: run pytest tests/test_io.py -q β€” note each test got its own fresh tmp_path; print it with p = tmp_path; print(p) once to see where pytest puts it.
  3. Add the monkeypatch test from the starter: it sets the ANALYZER_LOG_LEVEL env var and asserts your Day 16 get_log_level precedence function honors it β€” without polluting your real shell.
  4. Add a teardown demo: a fixture with yield that prints "setup" before and "teardown" after; run with pytest -s once to watch the order. Delete the prints after.
🐍 python β€” editable, runs in your browser
Ctrl/⌘+Enter runs · Tab indents · numpy/pandas/sklearn auto-load on import (torch and network calls need a local run)
guided 2

The pdb tour of a real crash

15 min
  1. Create crime_scene.py from the starter β€” it crashes. Terminal: run python crime_scene.py and read the traceback BOTTOM-UP: name the exception, the raising line, and the last frame in "your" code before looking further.
  2. Add breakpoint() on the line above the crash site and rerun. You are now in pdb at the frozen scene.
  3. Interrogate: p record, p record.get("status"), l to see surrounding code, n to step one line. Find the flawed assumption (some records have no status key).
  4. Quit (q), fix the function to handle the missing key, remove breakpoint(), and rerun clean.
  5. Now the professional finish: write the regression test test_summary_handles_record_without_status in your suite, watch it pass, and note it would have FAILED before your fix (check by temporarily reverting).
🐍 python β€” editable, runs in your browser
Ctrl/⌘+Enter runs · Tab indents · numpy/pandas/sklearn auto-load on import (torch and network calls need a local run)

On your own

Bisect the mystery

20 min

Download nothing β€” write mystery.py: a pipeline of six small functions chained together (normalize -> split -> filter -> parse -> aggregate -> format), where you deliberately plant one subtle bug in a function chosen by a coin flip (e.g. an off-by-one slice, a swapped comparison). Wait one hour if you can, or better: have the AI tutor plant the bug for you in a version you don't read.

Goal: find the buggy stage using bisection ONLY β€” check the intermediate value at the pipeline's midpoint, decide which half is guilty, and repeat. You get a maximum of 3 inspection points before naming the stage. Then confirm with pdb, fix, and write the regression test.

Hints: printing (or asserting on) the midpoint value tells you if corruption happened before or after it β€” that single bit halves the search space, exactly like Day 22's binary search will.

Ship before you stop

Harden the analyzer suite

Extend your analyzer's test suite to cover the I/O shell: file reading via tmp_path fixtures (valid file, file with malformed lines, empty file, missing file), env-var config via monkeypatch (both precedence directions), and at least one regression test reproducing a real bug you have personally hit since Day 14 β€” named test_regression_<description>, with a one-line comment citing the original symptom. Refactor anything that resists testing (a function that opens files AND computes β€” split it). Target: 15+ total tests, all green, no test touching the real filesystem outside tmp_path. Commit. This suite is a Day 21 shipping requirement, and its green run becomes a CI gate on Day 152.

Rubric β€” check what you completed (0/5)

Common mistakes & misconceptions

  • Reading a traceback top-down and fixating on the first frame (often framework code). Read bottom-up: exception line first, then the deepest frame in YOUR code.
  • Guess-editing before reproducing. If you cannot trigger the bug on demand with a minimal input, you cannot know your fix fixed it.
  • Testing file code against real project files. They change, tests break mysteriously, and a buggy test can overwrite real data β€” tmp_path exists precisely for this.
  • Mocking your own internals to force a test to pass. Boundary mocks (env, network, clock) are healthy; internal mocks mean the design needs splitting, not the test more magic.
  • Deleting the breakpoint()-driven insight without capturing it. The bug you just fixed is the cheapest test case you will ever get β€” rig the alarm while the scene is fresh.
  • Leaving breakpoint() calls in committed code. They freeze production processes; grep for breakpoint before every commit (a pre-commit hook can do this for you).
Knowledge check

Q1. A test needs a log file on disk. The right approach is…

Q2. Where do you look FIRST in a long traceback?

Q3. You fixed a bug found in production. What turns that incident into permanent protection?

Go deeper β€” curated resources

docspytest β€” How to use fixtures (official) β†—30 mincourseMIT Missing Semester β€” debugging & profiling lecture β†—30 mindocspytest Documentation β€” monkeypatch & tmp_path references β†—15 min
If you have a third hour
  • git bisect β€” When a bug appeared "sometime in the last 40 commits," git bisect binary-searches history: mark one good and one bad commit and it checks out midpoints until the guilty commit is found. Try it on your repo after Day 20 gives you more history.
Done means
  • I/O shell tested end to end via tmp_path; env precedence via monkeypatch
  • crime_scene.py debugged with pdb and its bug preserved as a passing regression test
  • Mystery pipeline bug found within 3 bisection probes
  • Suite at 15+ green tests, committed; quiz β‰₯ 2/3
How this connects

← Back: Day 18 taught alarms for pure logic; today the I/O shell from Day 5 and the config code from Day 16 got their alarms too. The bisection method is Day 22's O(log n) insight applied to detective work.

Forward β†’: Day 21 ships only when this suite is green. git bisect (same idea across commits) appears when history gets long; on Day 141 a failing eval is bisected across prompt changes, and Day 172 runs this whole method inside a customer's locked-down environment.