Key Takeaways: Testing and Debugging

One-Sentence Summary

Automated tests are code that checks other code — they catch bugs early, prevent regressions, and turn "it seems to work" into "I can prove it works."

Core Concepts

Concept What It Means Why It Matters
Unit test A test that checks one function in isolation Fast, focused, catches bugs at the source
Test-driven development Write the test before the code Forces you to design from the user's perspective
Edge case Input at the extreme boundary of what's valid Most bugs live at edges (empty lists, zero, negative numbers)
Fixture Shared setup data provided by @pytest.fixture Eliminates copy-paste in tests, ensures fresh state
Code coverage Percentage of code executed by tests Useful metric, but 100% doesn't mean correctness
Regression test A test that prevents a fixed bug from recurring Guards against "it used to work" surprises

The TDD Cycle

Step Color Action
1 Red Write a test. Run it. It fails.
2 Green Write the minimum code to make it pass.
3 Refactor Clean up without changing behavior. Tests still pass.
Repeat Write the next test.

pytest Quick Reference

Task Command or Syntax
Install pytest pip install pytest
Run all tests pytest
Run one file pytest test_module.py
Verbose output pytest -v
Run with coverage pytest --cov=module_name
Test naming Files: test_*.py, functions: test_*()
Basic assertion assert result == expected
Float comparison assert result == pytest.approx(expected)
Expect exception with pytest.raises(ValueError):
Fixture @pytest.fixture decorator, pass fixture name as test parameter

Debugging Strategy Ladder

Use these in order — start with the simplest and escalate if needed:

  1. Read the error message — Python traceback tells you the file, line, and exception type
  2. Print debugging — add print() calls to see intermediate values
  3. Rubber duck debugging — explain the code line by line out loud
  4. Binary search debugging — check the midpoint, narrow to the half that's broken
  5. VS Code debugger — set breakpoints, step through, inspect variables interactively

Writing Testable Code

Hard to Test Easy to Test
Calls input() and print() Takes parameters, returns values
Reads from hardcoded file paths Accepts file path as parameter
Depends on global state Pure function — no side effects
Does I/O and logic in one function Logic separated from I/O

Key Distinctions

What Beginners Think What Professionals Know
Testing is extra work Testing saves time in the long run
100% coverage = fully tested Coverage measures breadth, not depth
"It works on my machine" If there's no test, it doesn't work reliably
Write code first, test later TDD: define "working" first, then code
Tests are for big projects Even small scripts benefit from a few key tests

Common Edge Cases Checklist

Always test these for any function that accepts a collection or number: - Empty collection ([], "", {}) - Single element ([42], "x") - All identical values ([5, 5, 5]) - Zero (0, 0.0) - Negative values (-1, -100) - Boundary values (exactly at the threshold where behavior changes) - Very large values

What's Next

Chapter 14: Object-Oriented Programming — you'll bundle data and behavior into classes. TDD will help you design clean class interfaces from day one.