Q11 of 38 · Test design
How do you test a function with several boolean inputs?
Short answer
Short answer: For ≤ 4 booleans (16 combinations), test all combinations. For 5+, use pairwise to cover every pair, augment with property-based tests for invariants, and add explicit cases for the combinations the business cares most about.
Detail
Boolean inputs are tempting to under-test because each looks simple. The trap is the combinatorial explosion: n booleans gives 2^n combinations, and bugs often hide in unexpected combinations.
The decision tree:
- ≤ 3 booleans (≤ 8 combos) — test exhaustively. There's no excuse not to.
- 4 booleans (16 combos) — still exhaustive in most contexts; a few seconds of test time.
- 5–7 booleans (32–128 combos) — pairwise with an exhaustive subset for known-important combinations.
- 8+ booleans (256+ combos) — pairwise as baseline + explicit cases + look for whether some inputs are actually independent (split into separate functions if so).
Two senior moves worth mentioning:
Property-based testing complements combinatorial. With Hypothesis (Python) or fast-check (JS), you state invariants ("if
is_adminis true, the result must be visible regardless of other flags") and the framework explores combinations automatically, including edge cases you didn't think to enumerate.Decision tables for categorical-and-boolean mixes. If alongside the booleans there's a 3-value enum (
role: free | pro | enterprise), the table format scales cleaner than nested loops and reads better in code review.
The signal: you understand that boolean inputs scale combinatorially and have multiple tools for different scales, not "I'd test all combinations" or "I'd test the obvious ones."
// EXAMPLE
test_feature_visibility.py
import pytest
from itertools import product
from feature import is_feature_visible
# 4 booleans = 16 cases; cheap to enumerate.
@pytest.mark.parametrize(
"is_admin,in_beta,has_flag_x,is_paid",
list(product([True, False], repeat=4)),
)
def test_feature_visibility_is_correct(is_admin, in_beta, has_flag_x, is_paid):
expected = (is_admin or has_flag_x) and is_paid
assert is_feature_visible(is_admin, in_beta, has_flag_x, is_paid) == expected
# Property-based for an invariant — admin always sees, regardless of in_beta.
from hypothesis import given, strategies as st
@given(st.booleans(), st.booleans(), st.booleans())
def test_admin_always_sees_when_paid(in_beta, has_flag_x, is_paid):
if is_paid:
assert is_feature_visible(True, in_beta, has_flag_x, is_paid) is True