Home / Blog
Requirements

Testable User Stories: Acceptance Criteria as a Quality Contract

The bridge between what was asked for and what gets tested, and why vague acceptance criteria are a deferred argument about who is right.

A user story is a conversation starter. The format is useful because it keeps the focus on the person and the goal rather than on the implementation: "as a [role], I want [capability], so that [benefit]." What it isn't is a specification. The story says what and why. The acceptance criteria say how you know it's done. Treating a story as complete without acceptance criteria is like issuing a contract that describes the deliverable without specifying how disputes will be resolved.

Vague acceptance criteria don't defer the argument: they guarantee it. The argument just moves from the planning session, where it's cheap, to the UAT session, where it involves the developer, the tester, the product owner, the business stakeholder, and probably a schedule impact.

What the user story format does and doesn't give you

The three-part structure ("as a… I want… so that…") is productive because it keeps the conversation at the need level rather than the solution level, and it attaches every feature to a user and a purpose. A story without the "so that" clause is a feature request with no justification: it can't be prioritized against anything else, and it can't be tested meaningfully because nobody stated what success looks like.

What the format doesn't give you is precision. "I want to filter the customer list" could mean a single-field search, multi-criteria filtering with saved presets, a range filter for date fields, a full-text search across all columns, or all of the above. The story accepts all of those interpretations simultaneously. The acceptance criteria are where that ambiguity gets resolved, and where the team agrees on which interpretation they're building.

Two formats for acceptance criteria

Given/When/Then (Gherkin). A structured format borrowed from behavior-driven development: Given a precondition, When an action occurs, Then an observable outcome results. Each scenario is a complete, self-contained test case.

Given the customer list is showing all 1,247 active records
When I apply a status filter set to "Suspended"
Then the list shows only records with status "Suspended"
And the record count in the header reflects the filtered total
And the filter state persists if I navigate away and return

The Gherkin format is precise enough to be turned directly into automated test code, which is what makes it valuable beyond documentation: the acceptance criterion and the test are the same artifact. A tester doesn't have to interpret the criterion; a developer can read the test to understand exactly what the story requires.

Bullet criteria. Simpler and faster to write, appropriate when the behavior is straightforward enough that the Given/When/Then scaffolding adds ceremony without adding clarity. A checklist of observable conditions that must all be true for the story to be accepted.

Acceptance criteria: export to CSV
- Exported file contains all columns visible in the current view
- File name follows the pattern [entity]_[date]_export.csv
- Records reflect any active filters at the time of export
- Empty state (no records matching filter) exports a header-only file
- Export completes within 5 seconds for up to 10,000 records

Both formats are valid. The choice depends on the complexity of the behavior, the team's testing toolchain, and whether the criteria will be reviewed primarily by technical or business stakeholders. The Gherkin format reads well with both audiences; the bullet format is faster to produce and faster to review.

The one word to ban from acceptance criteria

The word is "appropriate," and it has many siblings: quickly, properly, correctly, user-friendly, intuitive, adequate, sufficient. Any acceptance criterion containing one of these words is not a criterion: it's a recorded disagreement about to be deferred.

"The system shall respond appropriately to invalid input" could mean: displays an error message, logs the event, rejects the input silently, redirects to an error page, or sends an alert to an administrator. The word "appropriately" selects among all of those without committing to any of them. The developer picks one. The tester may be testing a different one. The stakeholder expects a third. The UAT session is where all three find out.

Replace the adverb with a specification: "The system shall display an inline error message beneath the affected field within 500ms of submission, and the field shall retain its invalid value so the user can correct it without re-entering the rest of the form." That sentence has no ambiguity left in it. It has exactly one meaning. A tester can write an automated check against it without reading anyone's mind.

The story that looks done but isn't

There is a class of user story that gets accepted in sprint review, goes to done, and comes back three sprints later as a bug. The story was technically complete: it met every acceptance criterion that was written. The criteria weren't wrong; they were incomplete. The unhappy path wasn't covered. The edge case wasn't discussed. The non-functional behavior under load wasn't specified.

The three categories of criteria most often missing:

Error and exception cases. What happens when the API returns an error? When the file the user uploads is malformed? When a required field arrives null from an upstream system? Happy-path criteria are easy to write because they describe what everyone wants to happen. Exception criteria require deliberately imagining failure, which is uncomfortable enough that people skip it.

Non-functional requirements embedded in the story. Performance bounds (completes within N seconds), security constraints (requires authentication, logs the action), accessibility requirements (keyboard navigable, screen-reader compatible): these are story-level requirements, not architecture concerns, and they belong in the acceptance criteria of the stories that touch them. If they're not there, they're assumed to be out of scope.

State persistence and boundary behavior. Does the filter state persist across sessions? What happens at the boundary of a pagination page? What does the zero-state look like when there are no records? Boundary conditions are where the most defects live, and they're the ones least often captured in acceptance criteria written by people describing the typical case.

From acceptance criterion to test case

A well-written acceptance criterion maps to exactly one test case. If a criterion maps to zero test cases, it's untestable. If it maps to five, it's a composite that should be split. One-to-one is the signal that the criterion is both precise enough to verify and specific enough to be meaningful.

The practical workflow: once acceptance criteria are agreed upon by the BA, the developer, and the tester before development starts (the "three amigos" conversation), the tester can begin writing test cases before the feature is built. This is not a sequential dependency inversion; it's a check. If writing the test case is hard, the criterion is ambiguous. The difficulty of test case writing is diagnostic: it surfaces specification problems while the cost of fixing them is low.

The criterion is a contract between the person who asked for the feature and the team that built it. Like any contract, its value is proportional to its specificity. A vague contract protects nobody. A specific one tells everyone exactly what was agreed, what was delivered, and what would constitute a breach. In a project context, the breach is the defect, and the contract is the acceptance criterion. Write it to survive the argument that will happen if something goes wrong.

← Previous
KPIs That Survive the Boardroom