Skip to content

Decision Table Testing

Concept

When system behaviour depends on multiple conditions with multiple outcomes, a decision table maps every combination of condition values to the corresponding expected result.

Conditions          → Outcomes
C1 | C2 | C3  ...  → Action

Decision tables make hidden combinations visible. Every rule becomes a test case.


Structure

Element Description
Conditions Inputs or flags that determine outcome
Actions Expected outputs or behaviour
Rules One column per unique combination

Example: Discount System

Two conditions: VIP customer, order amount > $1000. Four rules, four distinct outcomes.

Condition Rule 1 Rule 2 Rule 3 Rule 4
is_vip T T F F
amount > 1000 T F T F
Discount 20% 10% 5% 0%
import pytest


def calculate_discount(is_vip: bool, amount: float) -> int:
    if is_vip and amount > 1000:
        return 20
    if is_vip:
        return 10
    if amount > 1000:
        return 5
    return 0


@pytest.mark.parametrize("is_vip, amount, expected_discount", [
    (True,  1500, 20),  # Rule 1: VIP + high amount
    (True,   500, 10),  # Rule 2: VIP + low amount
    (False, 1500,  5),  # Rule 3: not VIP + high amount
    (False,  500,  0),  # Rule 4: not VIP + low amount
])
def test_discount_decision_table(
    is_vip: bool,
    amount: float,
    expected_discount: int,
) -> None:
    assert calculate_discount(is_vip, amount) == expected_discount

The table makes it impossible to miss rule 3 — the non-VIP customer with a large order who still deserves a 5% discount.


Three-Condition Example

Three conditions → up to 8 rules. Not all combinations always produce different outcomes; some can be merged.

def check_content_access(
    logged_in: bool,
    verified: bool,
    has_subscription: bool,
) -> bool:
    return logged_in and verified and has_subscription


@pytest.mark.parametrize("logged_in, verified, has_subscription, can_access", [
    (True,  True,  True,  True),
    (True,  True,  False, False),
    (True,  False, True,  False),
    (True,  False, False, False),
    (False, True,  True,  False),
    (False, True,  False, False),
    (False, False, True,  False),
    (False, False, False, False),
])
def test_content_access(
    logged_in: bool,
    verified: bool,
    has_subscription: bool,
    can_access: bool,
) -> None:
    assert check_content_access(logged_in, verified, has_subscription) == can_access

When rules share the same outcome regardless of one condition, that condition is "don't care" (–) and the rules can be merged to reduce test count.


Reducing Rules

Technique When Result
Merge "don't care" Condition doesn't affect outcome Fewer rules
Group by outcome Same result for multiple combos Parameterised class
Split large table > 4 conditions Two smaller tables

When to Use Decision Tables

Scenario Use DT
Business rules with multiple flags Yes
Pricing / discount logic Yes
Access control (role + status + feature flag) Yes
Simple if/else with one condition No
Continuous numeric ranges No — use EP + BVA

Risks

Risk Description Mitigation
Missing rules Not all 2^n combinations covered Count expected rules: 2^n conditions
Redundant rules Same outcome, different combos Merge using "don't care"
Spec ambiguity Unclear what happens in edge combos Clarify with PO/dev before writing table