Writing Testable Code: Practical Patterns

Some code is genuinely hard to test not because testing itself is hard, but because the code’s design actively fights against it — a handful of patterns make the difference between a five-minute test and a genuinely frustrating one.

# HARD TO TEST: hidden dependencies, side effects buried inside the function
def process_order(order_id):
    order = database.query(f"SELECT * FROM orders WHERE id={order_id}")  # hidden DB call
    if datetime.now().hour > 22:                                          # hidden time dependency
        send_email("Processing overnight order")                          # hidden side effect
    payment_api.charge(order.total)                                        # hidden external call
    return order

# TESTABLE: dependencies passed in explicitly, easy to substitute with fakes
def process_order(order, current_time, email_service, payment_service):
    if current_time.hour > 22:
        email_service.send("Processing overnight order")
    payment_service.charge(order.total)
    return order

The Patterns That Actually Help

  • Dependency injection — pass in dependencies rather than reaching out to global state or creating them internally
  • Pure functions where possible — same input always produces the same output, with no side effects, trivially testable with no setup at all
  • Separate decision logic from side effects — a function that decides what to do can be tested without actually doing it

If writing a test for a piece of code feels disproportionately painful, that’s frequently a genuine signal about the code’s design, not just a testing tooling problem — treating that friction as useful design feedback, rather than something to work around, tends to produce better code overall, not just more testable code.