01 — Deterministic Unit Testing, AAA & Fixtures
At a glance
After completing this lesson you can:
- Structure bulletproof test suites using the Arrange-Act-Assert (AAA) pattern.
- Eliminate flaky tests by decoupling code from wall-clock time and non-deterministic randomness.
- Design reusable, isolated test fixtures using
pytest/unittest.
Time: ~45 minutes · Prerequisites: 04 — Context Managers & Exceptions
Reading Companion
- Book: Architecture Patterns with Python (Cosmic Python) — Harry Percival & Bob Gregory
- Chapter 1 & 3: Domain Modeling and A Brief Coupling and Abstraction Interlude (Testing business logic without databases or web frameworks).
Why this matters in production
A flaky test suite that fails 2% of the time in CI is worse than no test suite at all: developers start ignoring CI red lights, and real regressions slip into production. True unit tests must be 100% deterministic, running in milliseconds without relying on time.sleep(), live databases, or network calls.
Deep dive
1. The AAA Pattern
- Arrange: Set up test inputs, dependencies, and state.
- Act: Execute the single target unit of behavior.
- Assert: Check expected return value, state change, or exception.
Hands on
python3 phase0-foundations/04-testing-and-verification/01-unit-testing-and-fixtures/examples/01_demo.py
python3 phase0-foundations/04-testing-and-verification/01-unit-testing-and-fixtures/examples/02_trap.py
AI Trap & Code Review
# BAD AI-GENERATED CODE — FLAKY TIME DEPENDENCY
import time
def test_cache_expiration():
cache.set("key", "val", ttl_seconds=1)
time.sleep(1.05) # Flaky! If CI runner lags, test fails or wastes 1 second per test!
assert cache.get("key") is None
Exercises
- Rung 1 (
parse_duration_seconds): Convert string durations ("10s","5m","2h","1d") into total integer seconds. - Rung 2 (
TtlCache— Fix the AI Bug): Fix an in-memory TTL cache that usedtime.time()directly by injecting aclock_fn: Callable[[], float]for deterministic testing. - Rung 3 (
Parameterized Validator): Implement an email format validator with comprehensive boundary assertions. - Rung 4 (
InMemoryEventBus): Build an event bus supporting subscribe and publish with isolated subscriber failure handling. - Rung 5 (
DeterministicIdGenerator): Create an ID generator accepting a seeded PRNG for reproducible test runs.
Wrapping up
Can you explain?
- Why is
time.sleep()in unit tests a severe anti-pattern? - What makes a test truly a "unit" test versus an "integration" test?
- What is the Arrange-Act-Assert pattern?
Further Reading & References
- Martin Fowler: Mocks Aren't Stubs
- Google Testing Blog: Testing on the Toilet - Know Your Test Doubles
- Architecture Patterns with Python (Cosmic Python): Chapter 3 - A Brief Interlude
- pytest Documentation: Fixture Scope and Dependency Injection
Show reference solution notes
Solution & Engineering Notes — 01 Unit Testing & Fixtures
- Clock injection: Passing
clock_fn: Callable[[], float]allows unit tests to freeze or fast-forward time instantly (clock = 100.0, thenclock = 105.0) with zero real-world delays. - Deterministic IDs: Injecting sequence or PRNG generators ensures snapshots and assertion comparisons remain identical across every single test run.
Self-check
Run python3 -m unittest phase0-foundations/04-testing-and-verification/01-unit-testing-and-fixtures/test_exercises.py -v until green, then record it here. Saved to progress/rungs.json; commit it.
Completion edits PROGRESS.md; commit it with the lesson.