Why write tests, and what should I test first?
Tests are what let you refactor confidently. Start with pure functions holding real business logic, then add a test for every bug you fix so it cannot come back.
def test_factorial_base_cases():
assert factorial(0) == 1
assert factorial(1) == 1
def test_factorial_rejects_negatives():
with pytest.raises(ValueError):
factorial(-1)#What to test first
- Pure functions with real logic — calculations, parsing, validation. Easy to test, high value.
- Bugs you have fixed. Write the failing test first, then fix it. That bug can never silently return.
- Edge cases: empty input, one item, the maximum, zero, negative, null.
#What not to bother with
- Getters and setters with no logic.
- Third-party libraries — they have their own tests.
- Exact HTML output, which changes constantly for cosmetic reasons.
#Test behaviour, not implementation
# Brittle — breaks when you rename a private helper
assert calculator._internal_state == 5
# Robust — survives any refactor that keeps the behaviour
assert calculator.add(2, 3) == 5If your tests break every time you refactor without changing behaviour, they are testing the wrong thing.
#Arrange, act, assert
def test_average_ignores_empty():
scores = [] # arrange
result = average(scores) # act
assert result == 0 # assertOne behaviour per test, and a name that says what it checks. When it fails at 2am, the name should be enough.
#Coverage is a floor, not a goal
100% coverage with assertions that check nothing proves nothing. A well-chosen 60% that covers your real logic is worth more than 95% padded with getter tests.
#The real payoff
It is not catching bugs today. It is that six months from now you can restructure a module and know within seconds whether you broke anything. Without tests, every change is a gamble, so nobody changes anything, and the code rots.