Writing Tests Is Cheaper Than Therapy

There's a specific kind of anxiety that comes from deploying code you haven't tested. Your heart rate goes up. You refresh the error dashboard like it's a cricket score. You say "should be fine" out loud, to nobody.
Tests are the cure. They're also much cheaper than the alternative.

Start with the simplest possible test
# pricing.py
def apply_discount(price: float, percent: float) -> float:
return price - price * percent / 100
# test_pricing.py
from pricing import apply_discount
def test_ten_percent_off():
assert apply_discount(100, 10) == 90
def test_discount_never_negative():
assert apply_discount(50, 110) >= 0
Run pytest. The second test fails, because someone will absolutely type 110 into the discount field one day, and now you know your function will happily pay the customer to take the product.

def apply_discount(price: float, percent: float) -> float:
percent = min(max(percent, 0), 100)
return price - price * percent / 100
Green. Deep breath. That's the whole loop.
Write a test for every bug you fix
When a bug reaches you, before fixing it, write a test that reproduces it. Watch it fail. Fix the code. Watch it pass. Now that bug can never sneak back in quietly. These are called regression tests, and a codebase full of them is a codebase with a memory.
Test behaviour, not implementation
A good test says what should happen, not how. "A discount over 100% results in a free item" is a good test. "The function calls min and then max" is a test that breaks every time you refactor, and teaches your team to hate tests.
Use parametrize to test many cases cheaply
import pytest
@pytest.mark.parametrize("price,percent,expected", [
(100, 0, 100),
(100, 25, 75),
(100, 100, 0),
(100, -10, 100), # negative discount ignored
(100, 150, 0), # capped at 100%
])
def test_apply_discount(price, percent, expected):
assert apply_discount(price, percent) == expected
Five cases, one test function. Edge cases are where bugs live, so this is where you get the most value per line.
What not to obsess over
- 100% coverage. Coverage tells you what code ran, not whether it was checked. 80% meaningful coverage beats 100% of tests that assert nothing.
- Testing trivial getters. Spend your effort on logic: calculations, branching, parsing, money, dates.
- Perfect architecture before the first test. A messy test is better than a perfect test you never write.
The real benefit
Tests aren't mainly about catching bugs. They're about confidence to change things. With good tests you can refactor, upgrade libraries and delete old code without that tight feeling in your chest. That calm is worth more than any coverage badge. And unlike therapy, it runs in under a second.