Boundary Value Analysis — Testing Where Defects Hide

If equivalence partitioning tells you which groups to test, boundary value analysis tells you which specific values within those groups are most likely to reveal defects. Decades of testing experience have shown that bugs cluster at the boundaries between partitions — the exact points where valid becomes invalid, where one price tier ends and another begins, where a counter resets. Off-by-one errors, incorrect comparison operators, and missing edge-case handling are all boundary defects, and they are among the most common bugs in software.

Boundary Value Analysis — Probing the Edges

BVA selects test values at the exact boundary and immediately adjacent to it on both sides. For a range of 18-65, the boundary values are 17, 18, 19 (lower boundary) and 64, 65, 66 (upper boundary). These six values catch the vast majority of boundary-related defects.

# BVA applied to the insurance age field (valid range 18-65)
# Combined with EP partitions from the previous lesson

def bva_values(min_val, max_val):
    return [
        {"value": min_val - 1, "label": "Just below min", "expected": "Invalid"},
        {"value": min_val,     "label": "At minimum",     "expected": "Valid"},
        {"value": min_val + 1, "label": "Just above min", "expected": "Valid"},
        {"value": max_val - 1, "label": "Just below max", "expected": "Valid"},
        {"value": max_val,     "label": "At maximum",     "expected": "Valid"},
        {"value": max_val + 1, "label": "Just above max", "expected": "Invalid"},
    ]

age_boundaries = bva_values(18, 65)

print("Boundary Value Analysis — Age Field (Valid: 18-65)")
print("=" * 55)
print(f"{'Value':>6}  {'Label':<20} {'Expected':<10}")
print("-" * 55)
for b in age_boundaries:
    print(f"{b['value']:>6}  {b['label']:<20} {b['expected']:<10}")

# Real-world example: applying BVA to a multi-tier discount system
# 0-99 items:    no discount
# 100-499 items: 10% discount
# 500+ items:    25% discount

print("\n\nBVA — Quantity Discount System")
print("=" * 55)

tier_boundaries = [
    # Tier 1 → Tier 2 boundary
    {"value": 99,  "label": "Below Tier 2 boundary", "expected": "0% discount"},
    {"value": 100, "label": "At Tier 2 start",       "expected": "10% discount"},
    {"value": 101, "label": "Above Tier 2 start",    "expected": "10% discount"},
    # Tier 2 → Tier 3 boundary
    {"value": 499, "label": "Below Tier 3 boundary", "expected": "10% discount"},
    {"value": 500, "label": "At Tier 3 start",       "expected": "25% discount"},
    {"value": 501, "label": "Above Tier 3 start",    "expected": "25% discount"},
    # Lower edge
    {"value": 0,   "label": "Zero items",            "expected": "0% / empty cart"},
    {"value": 1,   "label": "Minimum order",         "expected": "0% discount"},
]

print(f"{'Qty':>5}  {'Label':<25} {'Expected':<15}")
print("-" * 55)
for b in tier_boundaries:
    print(f"{b['value']:>5}  {b['label']:<25} {b['expected']:<15}")

# The classic off-by-one bug
print("\n\nWhy BVA catches bugs:")
print("  Developer writes: if quantity > 100: apply_discount(10)")
print("  Requirement says: 100+ items get 10% discount")
print("  Bug: quantity=100 gets NO discount (should get 10%)")
print("  BVA catches this: testing at value 100 (at boundary) reveals the defect")
Note: The classic boundary defect is the off-by-one error caused by using the wrong comparison operator. A developer writes if age > 18 instead of if age >= 18, rejecting 18-year-olds who should be eligible. Or they write if quantity > 100 instead of if quantity >= 100, denying the discount to customers ordering exactly 100 items. These subtle bugs pass every test that uses values in the middle of the range but fail at the exact boundary. BVA is specifically designed to catch this category of defect.
Tip: When applying BVA to text fields, boundaries are not just about length — they include character set boundaries too. For a field that accepts “alphanumeric characters only,” test at the boundary between letters and non-letters: the last valid character (‘z’, ‘9’) and the first invalid character (space, ‘!’, ‘@’). For Unicode-aware applications, also test at the boundary between ASCII and multi-byte characters.
Warning: BVA is most effective when the boundaries are clearly defined in the requirements. If the requirement says “the field accepts a reasonable number of characters” without specifying min and max, you cannot apply BVA effectively — you first need to clarify the boundaries through requirements analysis (Chapter 9). Vague requirements are BVA’s kryptonite, which is another reason why requirements analysis must precede test design.

Common Mistakes

Mistake 1 — Testing only at the boundaries and skipping values just outside

❌ Wrong: For range 18-65, testing only 18 and 65 — the minimum and maximum.

✅ Correct: Testing 17 (just below min), 18 (at min), 19 (just above min), 64 (just below max), 65 (at max), 66 (just above max) — six values that cover both edges completely.

Mistake 2 — Applying BVA only to numeric fields

❌ Wrong: “BVA is only for numbers — I cannot use it on text fields or dates.”

✅ Correct: BVA applies to any ordered set: string length (min/max characters), dates (start/end of valid range), file sizes (min/max upload), list counts (min/max items in a cart). Anywhere there is a defined boundary, BVA applies.

🧠 Test Yourself

A developer implements a discount rule as if quantity > 100 but the requirement says “orders of 100 or more items receive a 10% discount.” Which BVA test value catches this defect?