4.5.3 · D3Software Engineering

Worked examples — Requirements — functional vs non-functional, user stories, acceptance criteria

3,335 words15 min readBack to topic

The scenario matrix

Think of every requirement task as a point in a grid. The columns are the operation you must perform; the rows are the awkward kinds of input that break naive handling — the analogue of "quadrants, zero, and limiting values" in a maths topic.

# Case class (the "quadrant") Why it's tricky Hit by example
C1 Pure FR — an ordinary behaviour Easy to spot, easy to under-specify Ex 1
C2 Pure NFR — a quality with a number People forget the unit/threshold Ex 2
C3 Mixed — one sentence hiding both Must be split, not classified once Ex 3
C4 Degenerate: the empty/zero input Happy path ignores it; bugs live here Ex 4
C5 Boundary / limiting value — max, first, last Off-by-one, overflow, "≤ vs <" Ex 5
C6 Story with the how smuggled in Feels done, but over-constrains design Ex 6
C7 Real-world word problem — raw stakeholder speech Vague; must extract role/goal/benefit Ex 7
C8 Exam twist — "is this testable?" trap "Looks nice" masquerades as an AC Ex 8
C9 Negative / error path — invalid action The system must refuse correctly Ex 9
C10 NFR crossing quadrants — one number, many story rows Threshold must appear in AC, not prose Ex 10

Below, each example is labelled with the cell it covers. Together they touch every row.

Figure — Requirements — functional vs non-functional, user stories, acceptance criteria

Ex 1 — Pure functional requirement (C1)

Forecast: before reading on — is this a verb or an adverb? Does it contain any number? Guess the classification.

  1. Ask the FR test. "Can I phrase it as 'The system shall <verb> …'?" Why this step? The parent's test for a functional requirement is exactly this template — if a verb fits, it is a behaviour, hence functional.
  2. Fit the verb. "The system shall mark a book as returned." The verb mark fits cleanly. Why this step? Naming the verb proves it's a thing the system does, not a quality of how it does it.
  3. Check for any hidden number. There is none — no time, no size, no "99%". Why this step? A number would signal a non-functional smuggled in (see C3). Its absence confirms pure FR.

Verify: Apply the verb-vs-adverb test — mark is the verb, and no adverb ("quickly", "securely") is present. Classification: FR. ✓


Ex 2 — Pure non-functional requirement (C2)

Forecast: is "feel instant" acceptable as written? Guess yes/no before step 3.

  1. Apply the NFR test. "Is it measurable with a number/unit, not a feature?" Why this step? The parent defines a non-functional requirement as a measurable quality. "Feel instant" is a quality (performance), so it's on the NFR side.
  2. Spot the defect. "Instant" has no number and no unit — it is an opinion, not a test. Why this step? An NFR that can't be measured can't be verified; two people will disagree forever about "instant".
  3. Repair it with a threshold + percentile.

    "The return operation shall complete in ≤ 300 ms for the 95th-percentile request." Why this step? A number and a percentile make it testable by a benchmark. The percentile matters because a single slow request shouldn't fail an otherwise-fast system.

Verify: Re-run the NFR test: it now has a number (300), a unit (ms), and a coverage figure (95%). Measurable → valid NFR. ✓


Ex 3 — One sentence hiding both (C3)

Forecast: how many separate requirements are hiding here — 1, 2, or 3? Guess before counting.

  1. Extract every verb. The only user verb is search. Why this step? Verbs are behaviours → functional. Isolating them stops us from merging quality talk into the feature. → FR: "The system shall let a customer search products."
  2. Extract every measurable quality. "within 2 seconds" (performance) and "10,000 concurrent users" (scalability). Why this step? Numbers with units are the fingerprint of NFRs. There are two distinct qualities, so two NFRs. → NFR-a (performance): "Search shall return results in ≤ 2 s." → NFR-b (scalability): "The ≤ 2 s target shall hold under 10,000 concurrent users."
  3. Count the pieces: 1 FR + 2 NFRs = 3 requirements. Why this step? Splitting is the whole lesson of C3 — one English sentence is not one requirement.

Verify: Each piece passes its own test: search is a verb (FR ✓); "≤ 2 s" and "10,000 users" each carry a number/unit (NFR ✓✓). Total pieces = 3. ✓


Ex 4 — The empty / zero input (C4, degenerate)

Forecast: how many AC do you need — one for the normal case, or more? Guess the count.

  1. Write the happy-path AC first.

    Given a cart with 2 items, When I remove one, Then the cart shows 1 item and the total updates. Why this step? Establishes the ordinary behaviour before we stress the boundary — the same way you'd solve the "quadrant I" case before the awkward ones.

  2. Now drive the count to zero — the degenerate input.

    Given a cart with 1 item, When I remove it, Then the cart shows an "empty cart" message and total = £0. Why this step? Zero is the analogue of a degenerate input: the naive code path ("show the item list") has nothing to show. The parent warns bugs live at boundaries like 0 items.

  3. Add a persistence check (a second hidden zero-state).

    Given a removed item, When I refresh the page, Then it is still absent. Why this step? Removing "in the UI only" is a classic degenerate bug: the display reaches zero but the stored state doesn't. Forecasting it now prevents it.

Verify: Three AC — happy path, empty-cart boundary, and persistence — so AC count = 3. Total after emptying = £0, an objective pass/fail. ✓


Ex 5 — Boundary / limiting value (C5)

Forecast: does "up to 10,000" mean 10,000 passes, or 9,999? Guess before step 2.

  1. Name the ambiguity. "up to 10,000" could mean ≤ 10,000 (10,000 included) or < 10,000 (10,000 excluded). Why this step? This is the "≤ vs <" trap — the off-by-one of requirements. One character changes the contract.
  2. Fix the boundary explicitly with three test points.

    Given 10,000 concurrent users, When all search, Then 95th-percentile ≤ 2 s. (the boundary itself must pass) Given 9,999 users, Then it also passes. (just below) Given 10,001 users, Then the system may degrade gracefully (queue or shed load), not crash. (just above) Why this step? Testing the value itself and one on each side pins the limit precisely — exactly how you test a boundary in maths by checking the point and its neighbours.

  3. State the "above-limit" behaviour. Graceful degradation ≠ undefined behaviour. Why this step? A limiting value has a "beyond the edge" region; a complete spec says what happens there instead of leaving it to chance.

Verify: Three boundary points (9,999 / 10,000 / 10,001) with the limit inclusive. The tester now has an unambiguous pass line at exactly 10,000. ✓


Ex 6 — The how smuggled into a story (C6)

Forecast: which of the three slots (role / goal / benefit) is broken? Guess before step 1.

  1. Check each slot against its job. Role = "user" ✓. Benefit = "pages load fast" ✓. Goal = "a Redis cache" ✗. Why this step? The goal must be a capability the user wants, not a solution the dev imagined. "Redis cache" is a design decision, not a user goal.
  2. Rewrite the goal in problem-space language.

    "As a user, I want pages to load quickly, so that I don't abandon the site." Why this step? Stories own the problem (goal + benefit); the how (Redis vs CDN vs nothing) is decided later in Software Design. Freeing the goal frees the team.

  3. Sanity-check against INVEST. Recall INVEST = Independent, Negotiable, Valuable, Estimable, Small, Testable. The new goal is Negotiable (many solutions still allowed) and Valuable (the benefit is real); the old "Redis cache" goal violated the N by locking the solution in. Why this step? INVEST is the story quality gate; the smuggled solution failed exactly the letter (N) we now satisfy, which is why the rewrite is objectively better and not just a matter of taste.

Verify: Rewritten story contains no implementation noun; goal is a user capability, benefit is retention. Passes the "problem-space" test. ✓


Ex 7 — Real-world word problem (C7)

Forecast: who is the role here — the customer, or the agent? Guess before step 1.

  1. Find the real user (role). The person doing the work is the support agent, not the customer. Why this step? The parent's template forces a real user; picking the wrong role builds a feature for the wrong person.
  2. Find the goal (a capability, not a solution). They want to see a customer's past purchases quickly — not "a database", not "an email parser". Why this step? "Sort it out" is a wish, not a goal. We convert the pain ("hunting through emails") into the missing capability.
  3. Find the benefit (the value test). So they resolve tickets faster / stop wasting time. Why this step? The benefit justifies the cost and later drives prioritisation (see Estimation and Story Points). → Story: As a support agent, I want to see a customer's purchase history on their profile, so that I can answer questions without searching old emails.
  4. Mine the hidden NFR. "waste ages" → speed matters. → NFR: "The purchase history shall load in ≤ 1 s for the 95th-percentile request." Why this step? The complaint was about slowness, which is a measurable quality — it belongs in an NFR with a number.

Verify: Role = support agent (the worker, ✓), goal = capability (no solution word), benefit = time saved, plus one measurable NFR (≤ 1 s). All slots filled. ✓


Ex 8 — Exam twist: is this testable? (C8)

Forecast: guess which one a computer could check without a human opinion.

  1. Apply the automatability test. Ask: could a re-runnable test decide pass/fail with fixed inputs and outputs? Why this step? The parent redefines "testable" as automatable and objective, not "a human clicks around and likes it".
  2. Judge (A). "clean and modern" is an opinion — two people disagree, no fixed output. Why this step? Anything requiring taste cannot be a pass/fail test → not a valid AC.
  3. Judge (B). It names an exact string format (£X.XX) and a location (top-right). A test can assert the rendered value matches a pattern. Why this step? Fixed, checkable output = objective pass/fail = valid AC. This is the format Test-Driven Development turns straight into an assertion.

Verify: (A) fails the automatability test (subjective); (B) passes (objective, pattern-checkable). Answer: B is testable, A is not.


Ex 9 — Negative / error path (C9)

Forecast: how many failure AC can you think of? Guess a number before reading.

  1. List the ways the action can go wrong. Unknown email, expired link, wrong old-password. Why this step? A complete spec covers every branch, not just success — the error paths are their own "quadrants".
  2. Write a Given-When-Then for each refusal.

    Given an email not in the system, When I request a reset, Then show a generic "if the account exists, a link was sent" message (no leak). Given a reset link older than 30 min, When I click it, Then show "link expired" and offer to resend. Given the correct link but a weak new password, When I submit, Then reject it and show the password rule. Why this step? Each error must fail safely and informatively. The unknown-email case also protects security (don't reveal which emails exist).

  3. Cross-check against NFR-security. The generic message is itself a security requirement surfacing inside an AC. Why this step? Negative paths often carry hidden NFRs; catching them here (not in prose) makes them testable.

Verify: Three refusal AC (unknown email / expired link / weak password), each with an objective outcome. Error-path count = 3. ✓


Ex 10 — One NFR across many story rows (C10)

Forecast: does the 2 s rule get written once, or three times? Guess.

  1. Recognise the crosscutting NFR. One quality (page load ≤ 2 s) applies to every user-facing story. Why this step? Crosscutting NFRs are the "same number, many rows" case — like a constant that reappears in every quadrant.
  2. Attach the threshold to each story's AC.

    Search: Given 10,000 products, When I search, Then results in ≤ 2 s (95th pct). Cart: Given a full cart, When I open it, Then it renders in ≤ 2 s (95th pct). Checkout: Given valid card details, When I pay, Then confirmation in ≤ 2 s (95th pct). Why this step? An NFR that lives only in a prose document is not verified per story. Copying the measurable threshold into each AC makes each story independently testable — the parent's rule that "AC is where NFRs get a measurable threshold".

  3. Keep the master NFR too. The prose NFR stays as the single source of truth; the three AC are its instances. Why this step? If the target changes to 1.5 s, you edit one master and re-derive the AC — avoiding three drifting copies.

Verify: 1 master NFR → 3 story-level AC, each carrying the identical ≤ 2 s / 95th-pct threshold. Threshold appearances in AC = 3. ✓


Where this connects

  • The workflow that generates these cases: Software Development Life Cycle and Agile and Scrum.
  • Turning acceptance criteria into automated tests: Test-Driven Development.
  • Deciding the how deliberately excluded from stories: Software Design.
  • Finding the right role for a story: Stakeholder Analysis.
  • Prioritising by benefit and sizing stories: Estimation and Story Points.

Flashcards

On this page, what does "AC" stand for and what shape do we write it in?
Acceptance criteria — specific pass/fail conditions for a story, written as Given–When–Then (context → action → expected outcome).
Which case class does a sentence hiding both a verb and a number belong to, and what must you do?
The "mixed" class (C3) — you must split it into separate FR(s) and NFR(s), not classify it once.
Why test the boundary value itself plus one on each side (e.g. 9,999 / 10,000 / 10,001)?
To resolve the "≤ vs <" ambiguity and pin the limit precisely, including the beyond-the-edge behaviour.
What makes an acceptance criterion "testable" in the exam sense?
It can be automated with fixed inputs/outputs and gives an objective pass/fail — not a human opinion like "looks clean".
In a real-world word problem, how do you pick the story's role?
Choose the actual user doing the work (e.g. the support agent), not whoever is mentioned first.
What does INVEST stand for, and which letter forbids naming the solution in a story?
Independent, Negotiable, Valuable, Estimable, Small, Testable — the N (Negotiable) forbids locking in the how.
Where must a crosscutting NFR threshold (e.g. ≤ 2 s) actually appear to be verifiable?
Copied into each affected story's acceptance criteria, while the prose NFR stays as the single source of truth.