treeify logo
Awesome Test Case Design20 techniques

Decision Tables

Decision Tables (Playbook)

Turn messy business rules into a complete, consistent, and testable set of cases. Decision tables map conditions (inputs/facts) to actions (outcomes/effects), making gaps and conflicts obvious.


What & Why

  • What: a tabular model conditions × actions that enumerates rules (columns) defining what to do for each combination of condition values.
  • Why: complex policies (pricing, eligibility, routing, access) often hide missing rules or conflicting actions. A decision table forces completeness and consistency, and generates a minimal yet sufficient set of tests.

Best for: combinable discounts, shipping eligibility, KYC/AML checks, feature flags/rollouts, access control (AuthZ), UI state rules, retry policies.


Steps (8-step recipe)

  1. List conditions as boolean or enumerations; define exact meaning of each value.
  2. Normalize ambiguous terms (e.g., “VIP” → concrete thresholds; “large order” → subtotal ≥ 100).
  3. List actions with observable effects (accept/reject, message ID, flag set, price adjustment).
  4. Draft rules (columns) covering distinct behavior; don’t explode into full cartesian product yet.
  5. Add a priority / resolution strategy (first-match wins, all-match apply, or weighted).
  6. Check completeness (no gaps) and consistency (no conflicting actions in one rule).
  7. Minimize: merge or eliminate redundant rules; aim for a small representative set.
  8. Generate tests: 1+ test per rule; add boundary inputs for conditions that use ranges.

Notation

  • Conditions: Y = true, N = false, = don’t-care (any), or enumerated values (EU/US/ROW).
  • Actions: explicit outcomes (e.g., ACCEPT, REJECT(code=...), APPLY 10%, DISABLE button).
  • Priority: top-to-bottom evaluation order if using first-match wins.

Worked Example A — Discount Code Combinability

Policy (normalized)

  • Code length: 1..16 and charset [A-Z0-9-] (see Boundary playbook).
  • Code may be combinable (combinable=Y|N).
  • Gift card promotion cannot be combined with percentage-off codes.
  • Code requires min spend (e.g., subtotal ≥ 50).
  • If multiple conflicts, show the most actionable message to the user.

Conditions

IDCondition (fact)Domain
C1Input valid (length/charset)Y/N
C2Code expiredY/N
C3CombinableY/N
C4Cart has gift cardY/N
C5Meets min spend (subtotal ≥ 50)Y/N
C6Code typepercent/fixed

Actions (observable)

A#Action
A1ACCEPT & apply discount
A2REJECT code=VALIDATION.code.expired
A3REJECT code=VALIDATION.code.length/charset
A4REJECT code=CONFLICT.code.not_combinable
A5REJECT code=VALIDATION.code.min_spend
A6Offer remove conflicting item (UI hint)

Rules (first-match wins)

RuleC1 ValidC2 ExpiredC3 CombinableC4 GiftCardC5 MinSpendC6 TypeActionsNote
R1NA3Invalid input takes precedence
R2YYA2Expired beats other checks
R3YNNYpercentA4 + A6Not combinable w/ gift card
R4YNYNA5Min spend not met
R5YNYA1Accept

Generated Tests (extract)

  • R1 — "SAVE 10" (inner space) → REJECT VALIDATION.code.charset; input preserved.
  • R2 — "SAVE10" expired → REJECT VALIDATION.code.expired.
  • R3 — "SAVE10" non-combinable percent + gift card → REJECT CONFLICT.code.not_combinable; UI offers removal.
  • R4 — "SAVE10" combinable but subtotal < 50REJECT VALIDATION.code.min_spend.
  • R5 — Happy path (any type) with min spend met → ACCEPT; badge, total update.

Pair this with Boundary & Equivalence for code format and Error Taxonomy → UX for message IDs: ../20-techniques/boundary-and-equivalence.md, ../40-api-and-data-contracts/error-taxonomy.md.


Worked Example B — Shipping Method Eligibility

Policy (normalized)

  • Free shipping for subtotal ≥ 100 (domestic only).
  • No shipping to embargoed regions.
  • Heavy items (weight > 20kg) require Freight (no standard/free).
  • Weekend dispatch disabled for Cold Chain items.

Conditions

IDConditionDomain
C1Region allowedY/N
C2DomesticY/N
C3Subtotal ≥ 100Y/N
C4Weight > 20kgY/N
C5Cold ChainY/N
C6Today is weekendY/N

Actions

A#Action
A1Offer Free shipping
A2Offer Standard shipping
A3Offer Freight shipping only
A4Reject all (no shipping)
A5Hide Weekend Dispatch option

Rules (priority: safety > offers)

RuleC1 RegionC2 DomesticC3 Subtot≥100C4 HeavyC5 ColdC6 WeekendActionsNote
R1NA4Embargo → no shipping
R2YYA3Heavy → Freight only
R3YYYNA1 + A2Domestic free + standard
R4YYYA2 + A5Cold + weekend → hide weekend
R5YNA2Default standard

Tests (extract)

  • R1 — Region embargoed → Reject all (A4) with message code POLICY.region.embargoed.
  • R2 — Heavy item → Freight only.
  • R3 — Domestic + ≥100 → Free + Standard visible.
  • R4 — Cold-chain on weekend → Standard only, weekend option hidden.
  • R5 — Else → Standard available.

Completeness & Consistency Checks

  • Completeness: every reachable combination of condition values hits at least one rule (no gaps).
  • Consistency: no rule column assigns conflicting actions (e.g., ACCEPT & REJECT).
  • Priority: for first-match wins, order the rules so that safety and validation rules come first.
  • Default rule: include an explicit fallback (e.g., “Else → Standard shipping”) to avoid silent gaps.
  • MECE: make condition domains mutually exclusive by adding tie-breakers (e.g., created_at, id in sorting rules).

From Table → Minimal Tests

Aim for 1 test per rule + boundaries on numeric/date conditions.

MC/DC-inspired selection (practical version)

  • For each condition, include a pair of tests that change only that condition and flip the action where possible.
  • This yields a small set that still demonstrates independence of conditions.

Example (Shipping)

  • Start with R3 (Free+Standard).
  • Flip C3 (subtotal < 100) keeping others same → expect Standard only (R5).
  • Flip C4 to Heavy → Freight (R2).
  • Flip C1 to embargoed → Reject (R1).
  • Flip C5 & C6 → hide weekend (R4).

Oracles & Evidence

  • Functional: API/DB mutations (shipping option list; discount applied), response codes, feature flags.
  • UX: presence/absence of options; message IDs; button disabled states.
  • API Contract: error taxonomy codes, schema invariants for returned options.
  • Non-functional: p95 latency for eligibility computation; consistent pagination if options are listed.
  • Evidence: structured logs (rule_id, conditions summary), metrics per rule hit, traces per evaluation.

Add a log like {rule_id:"R3", conditions:{domestic:true, subtotal_ge_100:true}} to make tests and prod issues diagnosable.


Anti-patterns

  • Implicit priority (ambiguous evaluation order).
  • Overlapping rules that both fire with conflicting actions.
  • Wordy conditions that hide real checks (“VIP user” vs “total_spend ≥ 1000 in 90d”).
  • No default rule.
  • Actions not observable (can’t assert outcome).
  • Ignoring boundary values for range-based conditions.

Review Checklist (quick gate)

  • Conditions are normalized; domains defined; booleans or enums
  • Actions are observable (API/DB/UX)
  • Rules have explicit priority and no conflicts
  • Completeness: all meaningful combos covered (with default rule)
  • Boundaries added for numeric/date conditions
  • Tests: 1+ per rule; MC/DC-style flips for key conditions
  • Evidence: logs with rule_id and condition snapshot; metrics per rule

CSV Seeds

Discount combinability

rule,input_valid,expired,combinable,gift_card,min_spend,code_type,expected,oracle
R1,N,-,-,-,-,-,REJECT VALIDATION.code.charset,resp400+message_id
R2,Y,Y,-,-,-,-,REJECT VALIDATION.code.expired,resp400+message_id
R3,Y,N,N,Y,-,percent,REJECT CONFLICT.code.not_combinable+offer_remove,resp409+ui_hint
R4,Y,N,Y,-,N,-,REJECT VALIDATION.code.min_spend,resp400+message_id
R5,Y,N,-,-,Y,-,ACCEPT,resp200+badge+total_change

Shipping eligibility

rule,region_allowed,domestic,subtotal_ge_100,heavy,cold_chain,weekend,expected
R1,N,-,-,-,-,-,REJECT_ALL
R2,Y,-,-,Y,-,-,FREIGHT_ONLY
R3,Y,Y,Y,N,-,-,FREE_AND_STANDARD
R4,Y,-,-,N,Y,Y,STANDARD_HIDE_WEEKEND
R5,Y,-,-,N,-,-,STANDARD

Contributor Template (copy/paste)

# Decision Table — <Topic>

## Conditions
- C1: <name> — <domain>
- C2: <name> — <domain>
...

## Actions
- A1: <observable>
- A2: ...

## Rules (priority: first-match wins | all-apply)
| Rule | C1 | C2 | ... | Actions | Note |
|-----:|:--:|:--:|-----|---------|------|
| R1   |    |    |     |         |      |
...

## Tests
- R1 — <input> → <expected> (oracle: <evidence>)
...

## Completeness & Consistency
- Gaps: <...>
- Conflicts: <...>
- Default rule: <...>

## Links
- Boundary & Equivalence: `../20-techniques/boundary-and-equivalence.md`
- Error taxonomy: `../40-api-and-data-contracts/error-taxonomy.md`

See also

  • Boundary & Equivalence: ./boundary-and-equivalence.md
  • State Models (for lifecycles): ./state-models.md
  • Error taxonomy → UX: ../40-api-and-data-contracts/error-taxonomy.md
  • Checklists: ../60-checklists/functional-coverage.md, ../60-checklists/api-coverage.md