User Stories, Acceptance Criteria and Definition of Done for Testers

In Agile, the user story replaces the traditional requirements document as the primary input for test design. But a user story alone โ€” “As a user, I want to reset my password” โ€” is too vague to test against. The acceptance criteria attached to the story define the specific, testable conditions that must be true for the story to be considered complete. And the Definition of Done (DoD) defines the team-wide standard that every story must meet beyond its individual acceptance criteria. Testers play a critical role in shaping all three.

From User Story to Testable Acceptance Criteria

A well-written user story with strong acceptance criteria gives the tester everything they need to design test cases without ambiguity. A poorly written story forces the tester to guess โ€” and guessing leads to missed coverage and wasted time.

# User story with acceptance criteria and derived test cases

user_story = {
    "id": "US-087",
    "story": (
        "As a registered user, I want to reset my password via email "
        "so that I can regain access to my account if I forget my password."
    ),
    "acceptance_criteria": [
        {
            "ac_id": "AC-1",
            "given": "I am on the login page",
            "when": "I click 'Forgot Password' and enter my registered email",
            "then": "A password reset email is sent within 60 seconds",
        },
        {
            "ac_id": "AC-2",
            "given": "I have received a reset email",
            "when": "I click the reset link within 30 minutes",
            "then": "I am taken to the 'Set New Password' page",
        },
        {
            "ac_id": "AC-3",
            "given": "I am on the 'Set New Password' page",
            "when": "I enter a valid password (8+ chars, 1 uppercase, 1 digit, 1 special)",
            "then": "My password is updated and I see a success confirmation",
        },
        {
            "ac_id": "AC-4",
            "given": "I have received a reset email",
            "when": "I click the reset link after 30 minutes",
            "then": "I see an error message: 'This link has expired. Please request a new one.'",
        },
        {
            "ac_id": "AC-5",
            "given": "I am on the Forgot Password page",
            "when": "I enter an email that is not registered",
            "then": (
                "I see the same confirmation message as AC-1 (to prevent email enumeration) "
                "but no email is actually sent"
            ),
        },
    ],
}

# Derive test cases from acceptance criteria
print(f"User Story: {user_story['id']}")
print(f"  {user_story['story']}\n")
print("Acceptance Criteria โ†’ Test Cases:")
print("=" * 60)
for ac in user_story['acceptance_criteria']:
    print(f"\n  {ac['ac_id']}:")
    print(f"    GIVEN: {ac['given']}")
    print(f"    WHEN:  {ac['when']}")
    print(f"    THEN:  {ac['then']}")

# Definition of Done โ€” team-wide standard
DEFINITION_OF_DONE = [
    "All acceptance criteria are met and verified by QA",
    "Code is peer-reviewed and merged to main branch",
    "Unit tests pass with >= 80% coverage on new code",
    "No open P1 or P2 defects for this story",
    "Regression suite passes (automated)",
    "Documentation updated (API docs, user guides if applicable)",
    "Product Owner has accepted the story in sprint review",
]

print("\n\nDefinition of Done (team-wide):")
print("=" * 60)
for item in DEFINITION_OF_DONE:
    print(f"  [ ] {item}")
Note: Notice that AC-5 (unregistered email) specifies a security requirement: the system must show the same message regardless of whether the email is registered, to prevent attackers from enumerating valid accounts. This is exactly the kind of detail that testers add during refinement. Without AC-5, a developer might display “email not found” โ€” which is technically helpful for users but creates a security vulnerability. Testers bring the defensive-thinking perspective that makes acceptance criteria robust.
Tip: Use the Given-When-Then (GWT) format for acceptance criteria whenever possible. This format is directly testable โ€” each GWT statement maps to one or more test cases. It also aligns with BDD tools like Cucumber and SpecFlow, making it straightforward to automate these criteria as executable specifications. If your product owner writes acceptance criteria in plain English, offer to convert them to GWT format during refinement.
Warning: The Definition of Done must include testing conditions โ€” not just development tasks. A DoD that says “code is merged and deployed” without mentioning testing means stories can be declared “done” without any QA verification. Advocate for testing-related items in your team’s DoD: acceptance criteria verified, regression suite passes, no open P1/P2 defects. The DoD is a team agreement, so raise this in the sprint retrospective if testing is missing from it.

Common Mistakes

Mistake 1 โ€” Accepting user stories without acceptance criteria

โŒ Wrong: “The story says ‘As a user, I want to reset my password.’ That is clear enough โ€” I will figure out the test cases myself.”

โœ… Correct: “Before this story enters a sprint, it needs acceptance criteria covering the happy path, error conditions (expired link, unregistered email), and security considerations (preventing email enumeration). I will draft these during refinement.”

Mistake 2 โ€” Treating the Definition of Done as optional or aspirational

โŒ Wrong: “The DoD says regression tests must pass, but we are behind schedule so we will skip regression this sprint.”

โœ… Correct: “The DoD is a non-negotiable team commitment. If regression tests cannot pass because of known defects, the story is not done. We either fix the defects or remove the story from the sprint so our delivered work genuinely meets the quality bar.”

🧠 Test Yourself

A user story’s acceptance criteria state: “When the user enters an unregistered email on the Forgot Password page, the system should show ‘Email not found’.” A tester raises a concern. Why?