Writing Tests Is Cheaper Than Therapy

· 2 min read · Syed Omar Faruk Towaha
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.

Cost of bugs
The later a bug is found, the more people, meetings and apologies it involves.

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.

A failing test
Cheap, fast, slightly humbling.
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

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.

// related

// prefer the terminal?

Open the terminal blog and type read writing-tests-cheaper-than-therapy.