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:
Read the error message — Python traceback tells you the file, line, and exception type
Print debugging — add print() calls to see intermediate values
Rubber duck debugging — explain the code line by line out loud
Binary search debugging — check the midpoint, narrow to the half that's broken
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.