L0 · Foundations that don’t expire
L0Go deeper5 min read

State transition and pairwise testing

For features where the bug is not in any one input but in the order they arrived, or in a combination nobody tried. Pairwise in particular turns a combinatorial explosion into a few dozen cases with almost no loss of power.

Two techniques for the bugs that are not in any single input.

State transition testing is used when a feature has modes. You list the states it can be in, the events that move it between them, and which of those moves should be impossible. The bugs are usually in the moves, not the states.

Pairwise testing, also called all-pairs or combinatorial testing, is used when several independent options multiply into too many combinations to try. It selects a small set of cases that still covers every pair of values at least once.

A vending machine has states.

Empty. Coins inserted. Item selected. Dispensing. Out of stock. The interesting bugs are never in a state, they are in the moves between states. Press refund after selecting. Insert a coin while it is dispensing. Select an item that sold out one second ago.

Now think about a pizza order form instead. Size, crust, four toppings, delivery or collection, a discount code. Nothing about the order matters, but the combinations run into the thousands.

Two different problems, and two techniques that handle them.

The terms you will hear

  • State. A mode the thing can be in: draft, paid, shipped, refunded.
  • Event, or trigger. The action that moves it between states: pay, cancel, refund, time passing.
  • Transition. One state plus one event, and the state it lands in.
  • Invalid transition. A move that should be refused. Where most defects in this technique are found.
  • Combinatorial explosion. The number of combinations growing faster than anyone can test.
  • Pairwise, or all-pairs. A set of cases covering every pair of option values at least once.

Order matters: state transition testing

Some features have modes, and the mode changes what everything else does. An order that is draft, paid, shipped, or refunded. A subscription that is trialling, active, past due, or cancelled.

The method is simple.

  1. List the states. Usually five to eight for one feature.
  2. List the events that move between them. Pay, ship, cancel, refund, time passing.
  3. Draw the grid. States down the side, events across the top.
  4. Mark the moves that should work, and the ones that should not.
  5. Test both. The allowed ones must work. The forbidden ones must fail cleanly, and that is where the bugs are.

Too many combinations: pairwise

Say a checkout has five choices: payment type (4 options), delivery (3), currency (2), member or not (2), and discount code or not (2). That is 4 times 3 times 2 times 2 times 2, which is 96 combinations. Add one more choice and you are past 200.

Nobody tests 200 combinations. So people test the four they can think of and hope.

Pairwise is the middle path. Most combination bugs involve just two values interacting, for example Apple Pay with a foreign currency. Pairwise picks a small set of cases that covers every possible pair at least once. For the 96 above, that is usually around a dozen cases.

You do not calculate it by hand. Tools do it, including a free one in most languages. The skill is knowing when to reach for it, and being able to say what it does and does not cover.

How to use them on a real story

  1. Ask whether the feature has modes. If the word "status" appears anywhere, draw the state grid.
  2. Ask whether choices multiply. If there are three or more independent options, count the combinations before testing anything.
  3. Do the forbidden moves first. They are quick, usually unspecified, and they find real defects. They are also good exploratory charters.
  4. Generate a pairwise set for the rest. A dozen cases beats guessing four.
  5. Add the realistic combination. The one your best customers actually use, whether or not the tool picked it.
  6. Say what you covered. "All pairs covered, no three-way combinations" is honest and useful in the release summary.

Combination bugs are rarely exotic. They are two ordinary settings that nobody happened to switch on at the same time.

A worked example

Here is both techniques on the gift-card feature, in about fifteen minutes.

states-and-pairs-gift-card.txt
STATE GRID: a gift card

STATES   issued, active, partly used, empty, voided, expired

EVENT ->        issue   redeem   refund order   void    expire
issued          -       ok       n/a            ok      ok
active          NO      ok       ok             ok      ok
partly used     NO      ok       ok             ok      ok
empty           NO      NO*      ok             ok      ok
voided          NO      NO       NO*            NO      NO
expired         NO      NO       ok*            ok      NO

  * = the interesting ones. Test these first.
  Results: redeeming an empty card correctly failed. Refunding an order
  paid with a voided card returned 500 rather than a clean error.  BUG
  Refund against an expired card was not specified at all.    QUESTION

PAIRWISE: checkout with a gift card

  choices   payment (card, Apple Pay, PayPal, gift only)   4
            delivery (standard, express, collection)        3
            member (yes, no)                               2
            discount code (yes, no)                        2
  total combinations 48. pairwise set: 12 cases.

  case  payment    delivery   member  code
  1     card       standard   yes     yes
  2     card       express    no      no
  3     Apple Pay  standard   no      yes
  4     Apple Pay  collection yes     no
  5     PayPal     express    yes     no
  6     PayPal     collection no      yes
  7     gift only  standard   no      no
  8     gift only  express    yes     yes
  ...   (12 in total, every pair covered at least once)

  found: case 8, gift card plus express delivery plus a discount code,
  charged the delivery fee twice.                              BUG

Two defects, and neither was in any single input. One came from a forbidden state move, the other from a three-way pile-up that a dozen well-chosen cases happened to include.

How to show you know it

  • A state grid with the forbidden moves marked. It reads as senior work and takes ten minutes.
  • A bug from an illegal transition. Refunding a voided card, or its equivalent in your product.
  • A pairwise set with the count. "48 combinations, 12 cases, all pairs covered." Precise, and honest about the limit.
  • An unspecified transition you turned into a question. As with naming the oracle, the gap is the finding.

Questions

Do I need a tool for pairwise?

For more than about twenty combinations, yes, and free ones exist for every language. Below that, choose by hand and spend the time on the state grid instead.

Does pairwise catch every combination bug?

No, and say so when you report it. It covers every pair, not every triple. Bugs needing three specific settings at once can slip through, which is why you add the realistic combination by hand.

When is a state grid overkill?

When the feature has no status and no lifecycle. A search box has no states worth drawing. An order, a subscription or a document workflow always does.

How does this relate to equivalence and boundaries?

Those handle one input at a time. These two handle order and interaction. Most stories need the first pair, and any story with a lifecycle or several options needs these as well.