Boundary Value Analysis
Concept
Most bugs live on the edges of valid ranges. BVA focuses specifically on the minimum, maximum, and values immediately adjacent to each boundary.
Partition edge: min-1 | min | min+1 ... max-1 | max | max+1
↑ ↑ ↑ ↑ ↑ ↑
invalid valid valid valid valid invalid
Standard Boundary Points
For a valid range [min, max], always test:
| Point | Value | Reason |
|---|---|---|
| min − 1 | just below lower bound | should be rejected |
| min | lower bound itself | should be accepted |
| min + 1 | just above lower bound | should be accepted |
| max − 1 | just below upper bound | should be accepted |
| max | upper bound itself | should be accepted |
| max + 1 | just above upper bound | should be rejected |
Why Boundaries Break
Developers write:
if age > 18: # wrong — excludes 18
if age >= 18: # correct
Off-by-one errors in conditions are among the most common bugs. BVA is the fastest way to expose them.
Example: Age Validation (18–65)
import pytest
def validate_age(age: int) -> str:
if age < 18:
return "too_young"
if age > 65:
return "too_old"
return "valid"
@pytest.mark.parametrize("age, expected", [
(17, "too_young"), # min - 1
(18, "valid"), # min (boundary)
(19, "valid"), # min + 1
(64, "valid"), # max - 1
(65, "valid"), # max (boundary)
(66, "too_old"), # max + 1
])
def test_age_boundary_values(age: int, expected: str) -> None:
assert validate_age(age) == expected
Six targeted tests. They catch what 50 random tests in the middle of the range never will.
String Length Boundaries
def validate_username(name: str) -> bool:
return 3 <= len(name) <= 20
@pytest.mark.parametrize("name, valid", [
("ab", False), # 2 chars — min - 1
("abc", True), # 3 chars — min
("abcd", True), # 4 chars — min + 1
("a" * 19, True), # 19 chars — max - 1
("a" * 20, True), # 20 chars — max
("a" * 21, False), # 21 chars — max + 1
])
def test_username_length_boundaries(name: str, valid: bool) -> None:
assert validate_username(name) == valid
BVA for Collections
When a system has a limit on items (e.g., max 5 items in a cart):
| Boundary | Test |
|---|---|
| 0 items | empty state |
| 1 item | minimum meaningful |
| 4 items | max − 1 |
| 5 items | max (limit itself) |
| 6 items | should be rejected |
Risks
| Risk | Description | Mitigation |
|---|---|---|
| Skipping min+1 / max-1 | Only testing exact boundary | Always include adjacent values |
| Floating-point precision | 0.1 + 0.2 ≠ 0.3 in Python | Use decimal.Decimal or epsilon comparison |
| Inclusive vs exclusive | Ambiguous spec ("up to 65" vs "under 65") | Clarify spec before writing tests |
BVA + EP Together
EP reduces test count by grouping similar inputs. BVA ensures boundaries between those groups are tested precisely. Always use them as a pair — EP without BVA leaves the most dangerous values untested.