Equivalence Partitioning — Reducing Test Cases Without Reducing Coverage

Testing every possible input to a system is impossible — a single text field that accepts 100 characters has more possible combinations than atoms in the universe. Equivalence Partitioning (EP) solves this by dividing the infinite input space into a manageable number of groups — called partitions or classes — where all values within a group are expected to produce the same behaviour. You then test one representative value from each partition, confident that if it works for one value in the class, it works for all of them.

Equivalence Partitioning — Maximum Coverage, Minimum Tests

The core principle is simple: if the system treats all values in a range the same way, testing one value from that range is sufficient. This dramatically reduces the number of test cases while maintaining coverage of every distinct behaviour.

# Equivalence Partitioning applied to an age field
# Requirement: Age must be 18-65 for standard insurance policy
#   Age < 18  → "Underage — not eligible"
#   Age 18-65 → "Eligible — standard policy"
#   Age > 65  → "Senior — requires supplementary review"

# Step 1: Identify equivalence partitions
partitions = [
    {"id": "EP1", "range": "age < 18",   "class": "Invalid (underage)",
     "expected": "Not eligible", "valid": False},
    {"id": "EP2", "range": "18 <= age <= 65", "class": "Valid (standard)",
     "expected": "Eligible — standard policy", "valid": True},
    {"id": "EP3", "range": "age > 65",   "class": "Invalid (senior)",
     "expected": "Requires supplementary review", "valid": False},
]

# Step 2: Select ONE representative value from each partition
test_values = [
    {"partition": "EP1", "value": 10,  "expected": "Not eligible"},
    {"partition": "EP2", "value": 35,  "expected": "Eligible — standard policy"},
    {"partition": "EP3", "value": 70,  "expected": "Requires supplementary review"},
]

# Additional partitions for non-numeric input
extra_partitions = [
    {"id": "EP4", "range": "empty input",     "class": "Invalid (missing)",
     "expected": "Age is required", "value": ""},
    {"id": "EP5", "range": "non-numeric",     "class": "Invalid (type error)",
     "expected": "Please enter a number", "value": "abc"},
    {"id": "EP6", "range": "negative number", "class": "Invalid (negative)",
     "expected": "Age must be a positive number", "value": -5},
    {"id": "EP7", "range": "decimal number",  "class": "Invalid (not integer)",
     "expected": "Please enter a whole number", "value": 25.5},
]

print("Equivalence Partitioning — Age Field")
print("=" * 65)
print(f"\n{'Partition':<6} {'Range':<20} {'Class':<22} {'Test Value':>10}")
print("-" * 65)
for p in partitions:
    tv = [t for t in test_values if t['partition'] == p['id']]
    val = tv[0]['value'] if tv else "—"
    print(f"{p['id']:<6} {p['range']:<20} {p['class']:<22} {str(val):>10}")

print(f"\nAdditional partitions (non-numeric inputs):")
for p in extra_partitions:
    print(f"  {p['id']}: {p['range']:<20} → Test: '{p['value']}' → {p['expected']}")

print(f"\nTotal: {len(partitions) + len(extra_partitions)} partitions → "
      f"{len(partitions) + len(extra_partitions)} test cases")
print(f"Without EP: potentially millions of input combinations")
print(f"With EP: 7 representative tests covering all distinct behaviours")
Note: Equivalence partitioning applies to outputs as well as inputs. If a system produces three different error messages based on different invalid inputs, those three error messages represent three output partitions — and each one needs at least one test case. Thinking about both input partitions and output partitions ensures you cover every distinct system behaviour, not just every distinct input type.
Tip: Always include partitions for "unexpected" input types, not just for valid/invalid values within the expected type. For a numeric field, your partitions should include: valid range, below range, above range, empty, non-numeric text, special characters, extremely large numbers, and negative numbers. These "type error" partitions frequently reveal defects because developers often validate the range but forget to handle non-numeric input entirely.
Warning: Equivalence partitioning assumes that all values within a partition are truly equivalent — the system handles them identically. If there is a hidden boundary or special case within a partition, EP alone will miss it. This is why EP is typically combined with Boundary Value Analysis (the next lesson), which tests at the edges where partitions meet. EP gives you the partitions; BVA gives you the critical values within them.

Common Mistakes

Mistake 1 — Creating too few partitions by only considering valid and invalid

❌ Wrong: Two partitions — "valid" and "invalid" — with one test case each.

✅ Correct: Multiple partitions distinguishing between different types of invalid input (below range, above range, wrong data type, empty, special characters) because each type may trigger different error handling code.

Mistake 2 — Testing multiple values from the same partition

❌ Wrong: For the valid age range 18-65, testing with ages 20, 25, 30, 35, 40, 45 — six tests that all exercise the same code path.

✅ Correct: Testing with one representative value from the valid partition (e.g. 35) and using the saved effort to test values from other partitions (empty input, negative numbers, non-numeric text).

🧠 Test Yourself

A shipping cost calculator charges $5 for orders under $50, free shipping for orders $50-$200, and $10 express-only for orders over $200. How many equivalence partitions exist for the order amount?