Q5 of 38 · Test design
What is state transition testing?
Short answer
Short answer: State transition testing models a feature as a finite-state machine — states the system can be in and the events that move it between them — then derives test cases from valid and invalid transitions.
Detail
State transition is the right technique when behaviour depends on history. A login form has states: logged out → logging in → logged in → locked out. A checkout flow has cart → checkout → payment → confirmation. Bugs in such systems usually involve transitions: forgetting to handle a state, allowing an invalid transition, or losing data on a transition.
The technique:
- Draw the state diagram — states as nodes, transitions as labelled arrows. Include the starting state and any terminal states.
- Identify valid transitions — for each state, which events move you to which next state, and what side effects occur.
- Identify invalid transitions — what shouldn't be allowed (e.g. paying before logging in). The system should refuse these gracefully.
- Derive test cases — at minimum: every state visited at least once (state coverage), every transition exercised at least once (transition coverage), and key invalid transitions explicitly tested.
Worked example for a simple subscription system. States: Trial → Active → Cancelled → Expired. Valid transitions include Trial→Active (user pays), Active→Cancelled (user cancels), Cancelled→Active (user resubscribes). Invalid: Trial→Cancelled (you can't cancel a trial), Expired→Active (must resubscribe via payment, not direct).
Test cases come straight from the diagram — TC1: Trial → pay → Active; TC2: Active → cancel → Cancelled; TC3: Trial → cancel → expect rejection (invalid); and so on.
The senior signal: also testing transition data integrity — does cancelling preserve user data, does resubscribing restore it, does expiry freeze it without deleting?
// EXAMPLE
subscription-states.md
States: Trial, Active, Cancelled, Expired
Valid transitions:
Trial -- pay() --> Active
Trial -- trialEnd() --> Expired
Active -- cancel() --> Cancelled
Cancelled -- resub() --> Active
Cancelled -- periodEnd() --> Expired
Invalid (must reject):
Trial -- cancel() --> X (no cancellable trial)
Expired -- resub() --> X (must repay, not direct)
Active -- pay() --> X (already active)
Test cases:
TC1 Trial → pay → Active
TC2 Trial → trialEnd → Expired
TC3 Active → cancel → Cancelled (verify data retained)
TC4 Cancelled → resub → Active (verify data restored)
TC5 Cancelled → period → Expired
TC6 Trial → cancel → expect 4xx + state unchanged
TC7 Expired → resub → expect redirect to payment