Equivalence Partitioning for State Machines and Multi-Variable Inputs
Equivalence partitioning for a single input is straightforward: identify the behavioral regions, pick one representative from each. Multi-variable and stateful systems are harder because the partitions aren't independent — the valid values for one variable depend on the state of another.
This post covers EP for systems with state and multiple interacting variables.
State-Dependent Partitions
In a stateless system, the same input always produces the same output. In a stateful system, the same input can produce different outputs depending on current state.
For a stateful system, your partition analysis must include state as a dimension.
The subscription system example
A subscription management system with states: FREE, TRIAL, ACTIVE, PAUSED, CANCELLED, EXPIRED.
Transitions:
FREE→TRIAL(start trial)FREE→ACTIVE(subscribe directly)TRIAL→ACTIVE(trial converted)TRIAL→CANCELLED(cancelled during trial)ACTIVE→PAUSED(pause subscription)ACTIVE→CANCELLED(cancel)PAUSED→ACTIVE(resume)PAUSED→CANCELLED(cancel while paused)ACTIVE→EXPIRED(payment failure)EXPIRED→ACTIVE(payment retry succeeded)CANCELLED→ACTIVE(resubscribe)
Partition structure for state machines
Partition class 1: Valid transitions Each valid transition is one test partition. For the subscription system: 10 valid transitions → 10 test cases.
Partition class 2: Invalid transitions from each state From FREE, you can't go to PAUSED, CANCELLED, or EXPIRED directly. Test one invalid transition per impossible state pair:
- From
FREEtoPAUSED→ error - From
CANCELLEDtoTRIAL→ error - From
EXPIREDtoPAUSED→ error - etc.
Partition class 3: Invalid operations independent of state Transitions that are invalid regardless of state: missing required fields, invalid payment method, etc.
For a system with 6 states, there are 6×5=30 possible transitions. If 10 are valid, then 20 are invalid. Testing one representative from each invalid pair = 20 invalid transition tests.
Total: 10 + 20 + N (invalid operation tests) = comprehensive state machine coverage.
State coverage matrix
Build a matrix to track partition coverage:
| From/To | FREE | TRIAL | ACTIVE | PAUSED | CANCELLED | EXPIRED |
|---|---|---|---|---|---|---|
| FREE | N/A | ✅ TEST | ✅ TEST | ❌ TEST INVALID | ❌ TEST INVALID | ❌ TEST INVALID |
| TRIAL | ❌ TEST INVALID | N/A | ✅ TEST | ❌ TEST INVALID | ✅ TEST | ❌ TEST INVALID |
| ACTIVE | ❌ TEST INVALID | ❌ TEST INVALID | N/A | ✅ TEST | ✅ TEST | ✅ TEST |
| PAUSED | ❌ TEST INVALID | ❌ TEST INVALID | ✅ TEST | N/A | ✅ TEST | ❌ TEST INVALID |
| CANCELLED | ❌ TEST INVALID | ❌ TEST INVALID | ✅ TEST | ❌ TEST INVALID | N/A | ❌ TEST INVALID |
| EXPIRED | ❌ TEST INVALID | ❌ TEST INVALID | ✅ TEST | ❌ TEST INVALID | ❌ TEST INVALID | N/A |
This matrix makes visible which transitions have been tested and which haven't. Complete coverage = every cell in the matrix has at least one test.
Multiple Interacting Input Variables
When multiple inputs interact to determine system behavior, partitions are no longer independent.
The insurance premium example
A simplified insurance premium calculator:
- Age: valid 18–80
- Vehicle type: car, truck, motorcycle
- Coverage level: basic, standard, premium
- Driver record: clean, minor violations, major violations
Each variable has its own partitions. But the interaction matters:
- Motorcycle + major violations = very high premium (maybe above threshold for coverage)
- Age 18 + motorcycle + major violations = potentially uninsurable
Simply testing each variable in isolation (all combinations with nominal values for others) misses the interactions.
Category-partition method
The Category-Partition Method (CPM), developed by Ostrand and Balcer, provides a systematic approach to multi-variable partitioning:
Step 1: Identify categories (the variables)
- Age
- Vehicle type
- Coverage level
- Driver record
Step 2: Define partitions within each category
Age:
- P1: Below minimum (< 18)
- P2: Young adult (18–25)
- P3: Adult (26–64)
- P4: Senior (65–80)
- P5: Above maximum (> 80)
Vehicle type:
- V1: Car
- V2: Truck
- V3: Motorcycle
Coverage level:
- C1: Basic
- C2: Standard
- C3: Premium
Driver record:
- D1: Clean
- D2: Minor violations
- D3: Major violations
Step 3: Identify constraints
Constraints restrict which partition combinations are valid:
IF vehicle_type = motorcycle AND age < 21 THEN record = clean ONLY(high-risk combination may not be offered)IF major violations THEN coverage = basic ONLY(major violations restrict coverage options)
Step 4: Generate test cases
Without constraints: 5 × 3 × 3 × 3 = 135 combinations. Too many.
With constraints applied (filtering impossible/redundant combinations): reduces to ~20–30 meaningful test cases.
The key insight: only test combinations that represent genuinely different behaviors or that specifically exercise a constraint. Combinations that differ only in values within the same partition (26 vs. 35 for "adult age") don't add information.
Pairwise Reduction for Multi-Variable EP
When full combinatorial coverage is impractical, pairwise testing reduces the set to cover all two-way interactions:
Principle: if most bugs are caused by one variable alone or by the interaction of two variables, testing all pairs of variable values provides high defect coverage with dramatically fewer test cases.
For the insurance example (5 × 3 × 3 × 3 = 135 combinations), pairwise reduction produces ~15–20 test cases while covering every (age_partition, vehicle_type) pair, every (vehicle_type, coverage_level) pair, etc.
Tools: PICT (Microsoft), AllPairs, Hexawise.
When to go beyond pairwise
Pairwise misses bugs in 3-way and higher interactions. Use t-way testing (t=3 or higher) when:
- The domain is safety-critical (medical devices, aerospace)
- You have specific reason to believe 3-way interactions exist
- You want to justify a higher coverage claim
For most commercial software, pairwise is sufficient. The marginal value of 3-way over 2-way is real but small.
Business Rule Partitions
Complex business rules create partitions that aren't obvious from the data types.
Progressive discount example
A pricing rule:
- No discount for quantities 1–9
- 5% discount for quantities 10–49
- 10% discount for quantities 50–99
- 15% discount for quantities 100+
Each discount tier is a partition. The boundaries (9/10, 49/50, 99/100) are boundary values within the EP framework.
But business rules often have compound conditions:
- 5% discount for quantities 10–49 AND customer type = RETAIL
- 8% discount for quantities 10–49 AND customer type = WHOLESALE
- No discount for customer status = BLACKLISTED regardless of quantity
This creates a 2D partition matrix:
| Quantity | RETAIL | WHOLESALE | BLACKLISTED |
|---|---|---|---|
| 1–9 | No discount | No discount | No discount |
| 10–49 | 5% | 8% | No discount |
| 50–99 | 10% | 13% | No discount |
| 100+ | 15% | 18% | No discount |
Test cases: one representative per cell = 12 test cases covering all meaningful combinations.
Identifying compound business rule partitions
Look for:
- IF-THEN-ELSE chains with multiple conditions
- Different behavior for different customer segments, geographies, or account types
- Overrides (the BLACKLISTED customer who overrides normal discounts)
- Priority rules (rule A and rule B both apply — which wins?)
Each combination of conditions that produces different output is a distinct partition. Document them in a matrix before writing tests.
Derived Variables and Calculated Partitions
Some variables are calculated from others. Their partitions are derived, not specified directly.
Risk score example
A risk score = (age_factor × 0.3) + (record_factor × 0.5) + (vehicle_factor × 0.2)
Where:
- age_factor: 1.0 for 25–60, 1.5 for <25 or >60
- record_factor: 1.0 for clean, 2.0 for minor, 3.0 for major
- vehicle_factor: 1.0 for car, 1.5 for truck, 2.0 for motorcycle
Risk score determines premium tier: < 1.5 = standard, 1.5–2.5 = elevated, > 2.5 = high-risk
The premium tier partitions are derived. To identify them, calculate the extreme values:
- Minimum risk: 1.0 × 0.3 + 1.0 × 0.5 + 1.0 × 0.2 = 1.0 (standard tier)
- Maximum risk: 1.5 × 0.3 + 3.0 × 0.5 + 2.0 × 0.2 = 0.45 + 1.5 + 0.4 = 2.35 (elevated tier)
Interesting finding: the formula makes it impossible to reach the "high-risk" tier (> 2.5). That's a business logic bug revealed by EP analysis.
For derived variables:
- Calculate the output range
- Identify which inputs produce which output partition
- Test input combinations that hit each output partition and the boundaries between them
Documenting Multi-Variable Partitions
For complex multi-variable systems, document with a decision table:
| Age | Vehicle | Record | Coverage | Expected Premium Tier |
|---|---|---|---|---|
| Adult | Car | Clean | Any | Standard |
| Young | Car | Clean | Any | Elevated |
| Adult | Motorcycle | Clean | Any | Elevated |
| Adult | Car | Major | Basic | Elevated |
| Adult | Car | Major | Standard | INVALID (not offered) |
| Young | Motorcycle | Major | Any | INVALID (not offered) |
Decision tables make coverage explicit, make invalid combinations visible, and serve as executable specifications.
Summary
Multi-variable EP requires identifying how variables interact — where the behavior of one depends on the value of another. The tools: state transition matrices for stateful systems, the Category-Partition Method for independent variables with constraints, and decision tables for complex business rules.
The reduction techniques — pairwise testing, constraint-based filtering — make comprehensive coverage tractable. The alternative (testing every combination or testing only the obvious cases) either produces too many tests or misses the interactions that cause real bugs.
The discipline: derive partitions from behavior. Different output or different processing = different partition. Same output for all values of a variable = one partition, one test.