Testing
Good tests pin down behavior and fail for one clear reason. Chase meaningful cases, not a coverage number.
What to test (priority order)
- The contract — for each function: typical input → expected output.
- Edge cases — empty, single element, very large, zero/negative, null/None, unicode, boundary values (off-by-one), duplicates.
- Error paths — invalid input rejected, exceptions raised/handled, partial failure.
- Regressions — every fixed bug gets a test that fails before the fix and passes after.
- Integration seams — where modules/services meet (fewer, broader tests here).
Shape: Arrange–Act–Assert
def test_discount_caps_at_zero():
cart = Cart(items=[Item(price=10)]) # arrange
total = cart.total(discount=2.0) # act (200% off)
assert total == 0 # assert: never negative
- One behavior per test; name it after the behavior (
test_discount_caps_at_zero). - Make failures readable: assert on specific values, not just "truthy".
Practical guidance
- Prefer many small fast unit tests + a few integration tests (the test pyramid).
- Keep tests deterministic: no real network/clock/randomness — inject or mock them.
- Mock at boundaries you own; don't mock the thing under test.
- For a bug: write the minimal failing test first, then fix until it's green.
- Run the suite to confirm, and report what passed/failed — don't claim green without running it.