Why Good Tests Matter
January 25, 2025
The Real Purpose of Good Tests: It's Multipurpose
Most people think tests exist to find bugs. While that's part of it, well-written tests serve multiple critical purposes that go far beyond defect detection.
The Multipurpose Nature of Good Tests
When tests are written correctly, they simultaneously:
- Find bugs (the obvious one)
- Document behavior (better than most documentation)
- Enable refactoring (confidence to change code)
- Improve design (hard-to-test code = poorly designed code)
- Reduce risk (deploy with confidence)
- Provide fast feedback (know immediately when something breaks)
The key phrase: "when written correctly." So what makes a test "good"?
The 12 Characteristics of Good Automated Tests
Based on industry best practices, good tests should be:
1. Automated
Run without human intervention. Click a button, get results. No manual steps, no manual verification.
2. Self-Evaluating
Tests check their own results. Pass or fail—no human needed to interpret output.
# Good: Self-evaluating
assert response.status_code == 200
assert response.data['user_id'] == expected_id
# Bad: Requires manual checking
print(response.data) # Someone has to look at this
3. Repeatable
Run it 100 times, get the same result every time. No flakiness, no environmental dependencies.
4. Independent
Each test stands alone. Order doesn't matter. Tests don't depend on each other's data or state.
5. Fast
Unit tests in milliseconds, integration tests in seconds. Slow tests don't get run, and tests that don't run have zero value.
6. Isolated
Tests don't interfere with each other. No shared state. No race conditions.
7. Maintainable
Easy to update when requirements change. Clear, readable, well-organized code.
Example of maintainable test structure:
def test_user_checkout_flow():
# Arrange
user = create_test_user()
product = add_product_to_cart(user)
# Act
result = checkout(user, product)
# Assert
assert result.success == True
assert result.order_id is not None
8. Trustworthy
When a test fails, there's actually a problem. When it passes, the feature actually works. No false positives or negatives.
9. Focused
Test one thing at a time. When it fails, you know exactly what broke.
10. Separated
Test code is separate from production code. Tests shouldn't pollute your system with test-specific logic.
11. Deterministic
Given the same input, always produces the same output. No randomness, no "sometimes it fails."
12. Informative
When a test fails, the error message tells you what went wrong and where to look.
# Bad: Uninformative
assert user.is_valid() # Why did this fail?
# Good: Informative
assert user.email is not None, f"User {user.id} missing email"
assert "@" in user.email, f"Invalid email format: {user.email}"
Why This Matters: The Multipurpose Test
Here's where it gets interesting. A test that has all 12 characteristics doesn't just find bugs—it serves multiple purposes simultaneously:
Purpose 1: Living Documentation
def test_refund_policy_full_refund_within_30_days():
order = create_order(days_ago=15)
refund = process_refund(order)
assert refund.amount == order.total
assert refund.fee == 0
This test documents the business rule: full refunds within 30 days, no fees. No need to dig through requirements docs—the test shows exactly how it works.
Purpose 2: Refactoring Safety Net
You want to optimize the checkout flow? With good tests, you refactor with confidence. Tests pass = behavior unchanged.
Purpose 3: Design Feedback
Struggling to write tests for a function? That's a code smell. Hard-to-test code is usually poorly designed code with too many dependencies or unclear responsibilities.
Purpose 4: Regression Prevention
That bug you fixed last month? The test you wrote ensures it never comes back.
Purpose 5: Continuous Deployment Enabler
You can't deploy 10 times a day without automated tests. Good tests make CI/CD possible.
The Cost of Bad Tests
Tests that violate these characteristics become:
- ❌ Maintenance burdens (brittle, hard to update)
- ❌ Time wasters (flaky, slow, unreliable)
- ❌ Obstacles to deployment (can't trust results)
- ❌ Sources of frustration (false failures)
Bad tests are worse than no tests because they create a false sense of security while slowing down development.
Practical Checklist
Before merging a new test, ask:
- Does it run automatically in CI/CD?
- Does it check its own results?
- Can I run it 10 times and get the same result?
- Does it run independently of other tests?
- Does it complete in seconds (not minutes)?
- Is the test code separate from production code?
- If it fails, will I know exactly what broke?
- Is the test name descriptive?
Conclusion
The purpose of tests isn't just finding bugs—it's enabling fast, confident software delivery.
When tests are:
- Automated and self-evaluating
- Fast and independent
- Maintainable and trustworthy
They become multipurpose tools that document behavior, enable refactoring, reduce risk, and provide fast feedback.
Good tests don't just find problems—they prevent them, document solutions, and enable continuous improvement.
Write tests with all 12 characteristics, and you'll discover they serve far more purposes than you originally intended.
What characteristics do your tests have? Which ones are missing? Let's discuss on LinkedIn.