Skip to content

Revision — Test Design Techniques Recap

This Part answered the question that sits between “I should test this” and “here is the test I wrote”: given one concrete function, screen, or API, which exact inputs do you actually run? The overview established the fact that makes the whole discipline necessary — you cannot test everything — and every page after it handed you a repeatable way to aim your limited budget at the places software actually breaks.

This revision walks the spine one more time, in prose and from memory, so you can reconstruct the toolkit without flipping back to the diagrams. The goal is not to re-teach each technique but to hold all six in your head at once, see the bug class each one hunts, and — most importantly — see how they stop being six separate tricks and become one intentional test suite.

Start from the number that forces everything else. A function that adds two 32-bit integers has about 1.8 × 10^19 possible input pairs — roughly 584 years to run at a billion cases per second, for addition. Add a string or a timestamp and the count passes the age of the universe. Exhaustive testing is not expensive; it is impossible, even for trivial code.

That leaves exactly one honest description of what a test suite is:

Every real test suite is a tiny, deliberate sample of an unbounded input space.

So the only question test design ever asks is which sample. Two facts make the answer tractable, and they are worth reciting because every technique in this Part is a consequence of one or both:

  1. Bugs cluster. They pile up at the edges of ranges, the transitions between states, the surprising combinations of options, and the values a programmer forgot existed. They are not spread evenly across the input space.
  2. Most inputs are redundant. For a given branch of logic, thousands of inputs are treated identically, so one of them exercises that branch exactly as well as all of them.

Put those together and a strategy falls out: skip the redundant middle, aim at the seams. The alternative — picking cases at random — is quietly wasteful, because random darts almost never land on the single boundary value where an off-by-one lives, and worse, they leave you with no coverage you can name. Effort you cannot bank as nameable coverage is not confidence; it is just activity.

TEST DESIGN
|
+-------------------+--------------------+
| |
INPUT-SPACE techniques BEHAVIOR techniques
"which values?" "which logic / sequences?"
| |
equivalence partitioning decision tables
boundary value analysis state-transition testing
pairwise / combinatorial
| |
+--------------- error guessing ---------+
(experience aimed at both)

The six techniques and the bug each one catches

Section titled “The six techniques and the bug each one catches”

Every technique in this Part is a concrete answer to “where does this break?” Here they are, each with the specific class of defect it exists to flush out.

Equivalence partitioning — forgotten categories

Section titled “Equivalence partitioning — forgotten categories”

Equivalence partitioning is the redundancy fact made into a procedure. You carve the infinite input space into classes that behave alike, then test one representative per class instead of the whole class. A field accepting ages might split into negative, 0–17, 18–130, and over 130 — four classes, four tests, full logical coverage of what the field does with a value.

The bug it catches is the whole silently-mishandled category: the negative number nobody considered, the empty string, the value past the documented maximum. Partitioning forces you to name every class, including the invalid ones, so an entire kind of input can no longer slip through untested.

Boundary value analysis is the cluster fact made into a procedure, and it is the inseparable partner of partitioning: partitioning tells you where the boundaries are, BVA tells you to hammer them. For each class edge you test the value on the boundary and the values immediately on either side of it.

The bug it catches is the one that lives exactly at the seam: the off-by-one (age > 18 where the spec said “18 and over”), the wrong operator (< where <= was meant), and the overflow at the limit where a value one step too large corrupts everything downstream. These are invisible in the comfortable middle of a range and only surface when you test precisely on the edge — which is why boundaries are where bugs cluster and BVA is how you get there.

Decision tables — missing and contradictory rules

Section titled “Decision tables — missing and contradictory rules”

Decision tables are the first tool built for combinations rather than single values. When behaviour depends on several conditions together — “discount if member and cart over $50 or holiday weekend” — you lay every combination of conditions out as a grid, one rule per column, with the required action for each.

The bug it catches is the combination nobody thought to specify: the missing rule (a mix of conditions the spec never addressed) or the contradictory rule (two columns that demand different actions for the same inputs). Testing one input at a time can never guarantee you hit the specific blend that hides the defect; enumerating the grid makes the forgotten combination impossible to overlook.

State-transition testing — illegal sequences

Section titled “State-transition testing — illegal sequences”

State-transition testing handles anything with modes, sessions, or a lifecycle — an order that moves placed → paid → shipped → delivered, a login that locks after three failures. You model the states and the events that move between them, then test both the valid transitions and the ones that should be refused.

The bug it catches is the illegal transition: the action allowed in the wrong mode (refunding an unpaid order), the stuck state you can enter but never leave, the event that silently corrupts state instead of being rejected. Where decision tables reason about a single combination frozen in time, state machines reason about order — the sequence in which events arrive.

Pairwise and combinatorial testing — toxic combinations

Section titled “Pairwise and combinatorial testing — toxic combinations”

Pairwise testing attacks the combinatorial explosion that a full cross-product of options would cause. Its empirical foundation is that most combination bugs are triggered by the interaction of just two option values, not by some exotic five-way conspiracy. So you construct a small set of cases that covers every pair of values at least once, and skip the astronomically larger full grid.

The bug it catches is the specific combination of settings or flags that fails — the Safari + dark mode + RTL layout that breaks when each option works fine alone and in every other pairing. It turns “test all combinations” (impossible) into “test every pair” (a handful), which is where combination bugs mostly hide.

Error guessing — what the spec never imagined

Section titled “Error guessing — what the spec never imagined”

Error guessing and experience-based testing is the one technique that is deliberately not mechanical. It aims tests using known failure patterns and intuition — the accumulated scar tissue of every bug you have seen before.

The bug it catches is the one no specification mentions, because the spec described the happy path: null, empty, zero, negative, the absurdly large value, the emoji in a name field, the leap day, February 30th, the timezone that shifts by 30 minutes. Error guessing is unstructured on purpose, which is both its strength (it reaches inputs no formal method derives) and its limit (its coverage cannot be named the way the others’ can) — so it supplements the systematic techniques rather than replacing them.

The six techniques are not a menu you pick one item from. On a real feature they stack, each catching what the previous one structurally cannot, and the combining techniques page walked exactly that layering on a live example. The natural order runs from carving the space down to guessing at what is left:

1. PARTITION every field → which categories exist? (incl. invalid ones)
2. BOUNDARY-test each class edge → the off-by-ones at the seams
3. DECISION-TABLE the tangled → the missing/contradictory rule combinations
if/and/or logic
4. STATE-MODEL any lifecycle → the illegal / out-of-order sequences
5. PAIRWISE the independent → the toxic two-option combinations
option sets
6. ERROR-GUESS the rest → the weird inputs no spec imagined

Each layer has a blind spot that the next one covers. Partitioning assumes every value in a class behaves alike — boundaries prove where that assumption snaps. Partitioning and boundaries both treat inputs as independent — decision tables and pairwise handle the combinations they cannot. All of those reason about a single moment — state-transition testing reasons about sequence. And every structured method reasons only about what someone specified — error guessing reaches the inputs the spec never mentioned. Used alone, any one technique leaves a shaped hole; layered, they close each other’s gaps.

That layering is also a budget, not a checklist to run to exhaustion. You do not partition every field to death and pairwise every option; you apply each technique where its bug class actually lives — boundaries on the numeric ranges, a decision table on the one genuinely tangled rule, a state model on the one real lifecycle — and you stop when the remaining cases would only re-cover ground you can already name. The output is not the maximum number of tests; it is the minimum set that covers every seam you can identify, with each case traceable to the specific defect it guards against.

Hold on to the single idea that runs under every page of this Part. Confidence is not bought with the quantity of tests — a million random inputs prove almost nothing you can name, because you cannot say which behaviours they touched or which they skipped. Confidence is earned by testing the places software is most likely to break, and being able to state, precisely, which of those places you covered.

That is what each technique buys you, translated into a class of bug you can name: partitioning finds the forgotten categories, boundary analysis finds the edges, decision tables find the missing rules, state machines find the illegal sequences, pairwise finds the toxic combinations, and error guessing finds what the spec never imagined. Boundaries are where bugs cluster; combinations are where they hide; the toolkit exists to reach both on purpose.

Test design is how the mindset from the start of this book — reason about where things break — becomes a small, deliberate, defensible set of cases. Choosing your test cases with intent, and knowing exactly what each one defends, is precisely how you earn justified confidence about the edges that break software. With the toolkit for which cases to run now in hand, the book turns next to putting those cases to work.