Equivalence Partitioning for State Machines and Multi-Variable Inputs

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 FREE to PAUSED → error
  • From CANCELLED to TRIAL → error
  • From EXPIRED to PAUSED → 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:

  1. Calculate the output range
  2. Identify which inputs produce which output partition
  3. 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.

Read more

Start now free