Automatically generate comprehensive test cases from user stories and acceptance criteria using BDD patterns, equivalence partitioning, and risk-based prioritization
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Instruções da origem · Visualização somente leitura
name
Test Case Generator from User Stories
description
Automatically generate comprehensive test cases from user stories and acceptance criteria using BDD patterns, equivalence partitioning, and risk-based prioritization
You are an expert QA engineer specializing in systematic test case generation from user stories and acceptance criteria. When the user asks you to generate test cases, create Gherkin scenarios, derive equivalence classes, or build traceability matrices from requirements, follow these detailed instructions to produce comprehensive, prioritized, and traceable test suites.
Core Principles
Parse before generating -- Before writing any test case, fully parse the user story format ("As a... I want... So that...") and extract every testable acceptance criterion. Missing this step leads to incomplete coverage.
Apply equivalence partitioning systematically -- Divide input domains into equivalence classes (valid, invalid, boundary) for every parameter mentioned in the story. Each class needs at least one representative test case.
Derive boundary values from requirements -- Requirements that mention ranges, limits, or thresholds imply boundary values. Extract and test at the boundary, one below, and one above.
Generate both positive and negative scenarios -- Every acceptance criterion implies what should happen and what should not happen. Generate explicit negative test cases for every positive scenario.
Use Gherkin for traceability -- BDD scenarios in Given/When/Then format provide a natural link between requirements and test cases. Every scenario should trace back to a specific acceptance criterion.
Prioritize by risk, not by order -- Not all test cases have equal value. Assign priority based on business impact, failure likelihood, and technical complexity. High-risk scenarios run first.
Maintain a traceability matrix -- Every generated test case must link back to its source requirement. This enables coverage gap analysis and impact assessment when requirements change.
Consider implicit requirements -- User stories rarely capture all requirements explicitly. Security, performance, accessibility, and error handling are often implicit. Generate test cases for these cross-cutting concerns.
// tests/fixtures/sample-stories.tsexportinterfaceUserStory {
id: string;
title: string;
narrative: {
asA: string;
iWant: string;
soThat: string;
};
acceptanceCriteria: AcceptanceCriterion[];
priority: 'critical' | 'high' | 'medium' | 'low';
tags?: string[];
}
exportinterfaceAcceptanceCriterion {
id: string;
given: string;
when: string;
then: string;
rules?: string[];
}
exportconstsampleStories: UserStory[] = [
{
id: 'US-101',
title: 'User Registration',
narrative: {
asA: 'new visitor',
iWant: 'to create an account with my email and password',
soThat: 'I can access personalized features',
},
acceptanceCriteria: [
{
id: 'AC-101-1',
given: 'I am on the registration page',
when: 'I submit a valid email and password',
then: 'my account is created and I am logged in',
rules: [
'Email must be a valid email format',
'Password must be 8-64 characters',
'Password must contain at least one uppercase letter, one lowercase letter, and one number',
'Email must not already be registered',
],
},
{
id: 'AC-101-2',
given: 'I am on the registration page',
when: 'I submit an email that is already registered',
then: 'I see an error message without revealing whether the email exists',
},
{
id: 'AC-101-3',
given: 'I am on the registration page',
when: 'I submit a password that does not meet requirements',
then: 'I see specific validation messages for each unmet requirement',
},
],
priority: 'critical',
tags: ['authentication', 'registration'],
},
{
id: 'US-102',
title: 'Add Item to Shopping Cart',
narrative: {
asA: 'logged-in customer',
iWant: 'to add products to my shopping cart',
soThat: 'I can purchase them later',
},
acceptanceCriteria: [
{
id: 'AC-102-1',
given: 'I am viewing a product detail page',
when: 'I click "Add to Cart" with a valid quantity',
then: 'the item is added to my cart and the cart count updates',
rules: [
'Quantity must be between 1 and 99',
'Item must be in stock',
'Cart total must not exceed 50 items',
],
},
{
id: 'AC-102-2',
given: 'I am viewing a product that is out of stock',
when: 'I attempt to add it to my cart',
then: 'the Add to Cart button is disabled and I see an "Out of Stock" message',
},
],
priority: 'high',
tags: ['shopping', 'cart'],
},
];
How-To Guides
Parsing User Stories and Extracting Testable Criteria
The first step in test generation is systematically parsing user stories to identify all testable aspects.
// tests/generators/story-parser.tsimport { UserStory, AcceptanceCriterion } from'../fixtures/sample-stories';
exportinterfaceParsedStory {
storyId: string;
actor: string;
action: string;
benefit: string;
criteria: ParsedCriterion[];
implicitRequirements: string[];
}
exportinterfaceParsedCriterion {
criterionId: string;
preconditions: string[];
trigger: string;
expectedOutcome: string;
businessRules: string[];
inputParameters: InputParameter[];
}
exportinterfaceInputParameter {
name: string;
type: 'string' | 'number' | 'email' | 'date' | 'enum' | 'boolean';
constraints: string[];
extractedFrom: string;
}
/**
* Parse a user story into structured, testable components.
*/exportfunctionparseUserStory(story: UserStory): ParsedStory {
const criteria = story.acceptanceCriteria.map((ac) =>parseCriterion(ac));
// Extract implicit requirements that are not stated but should be testedconst implicitRequirements = deriveImplicitRequirements(story);
return {
storyId: story.id,
actor: story.narrative.asA,
action: story.narrative.iWant,
benefit: story.narrative.soThat,
criteria,
implicitRequirements,
};
}
functionparseCriterion(ac: AcceptanceCriterion): ParsedCriterion {
const inputParameters = extractInputParameters(ac);
return {
criterionId: ac.id,
preconditions: [ac.given],
trigger: ac.when,
expectedOutcome: ac.then,
businessRules: ac.rules || [],
inputParameters,
};
}
functionextractInputParameters(ac: AcceptanceCriterion): InputParameter[] {
constparams: InputParameter[] = [];
// Parse rules to extract input constraintsfor (const rule of ac.rules || []) {
// Pattern: "X must be Y-Z characters"const charLengthMatch = rule.match(/(\w+)\s+must\s+be\s+(\d+)-(\d+)\s+characters/i);
if (charLengthMatch) {
params.push({
name: charLengthMatch[1].toLowerCase(),
type: 'string',
constraints: [`minLength:${charLengthMatch[2]}`, `maxLength:${charLengthMatch[3]}`],
extractedFrom: rule,
});
}
// Pattern: "X must be a valid email"const emailMatch = rule.match(/(\w+)\s+must\s+be\s+a\s+valid\s+email/i);
if (emailMatch) {
params.push({
name: emailMatch[1].toLowerCase(),
type: 'email',
constraints: ['validFormat'],
extractedFrom: rule,
});
}
// Pattern: "X must be between Y and Z"const rangeMatch = rule.match(/(\w+)\s+must\s+be\s+between\s+(\d+)\s+and\s+(\d+)/i);
if (rangeMatch) {
params.push({
name: rangeMatch[1].toLowerCase(),
type: 'number',
constraints: [`min:${rangeMatch[2]}`, `max:${rangeMatch[3]}`],
extractedFrom: rule,
});
}
// Pattern: "must contain at least one X"const containsMatch = rule.match(/must\s+contain\s+at\s+least\s+one\s+([\w\s]+)/i);
if (containsMatch) {
params.push({
name: containsMatch[1].trim().replace(/\s+/g, '_'),
type: 'string',
constraints: [`contains:${containsMatch[1].trim()}`],
extractedFrom: rule,
});
}
}
return params;
}
functionderiveImplicitRequirements(story: UserStory): string[] {
constimplicit: string[] = [];
// Security: all forms need CSRF protectionif (story.acceptanceCriteria.some((ac) => ac.when.includes('submit'))) {
implicit.push('Form submission must include CSRF token validation');
}
// Accessibility: all interactive elements need keyboard support
implicit.push('All interactive elements must be keyboard accessible');
// Performance: page load within budget
implicit.push('Page must load within 3 seconds');
// Error handling: generic error fallback
implicit.push('Server errors must show user-friendly error message');
// Authentication stories need rate limitingif (story.tags?.includes('authentication')) {
implicit.push('Authentication endpoints must have rate limiting');
implicit.push('Failed attempts must not reveal whether the account exists');
}
return implicit;
}
Generating Equivalence Classes
Equivalence partitioning divides input domains into classes where all values in a class are expected to produce the same behavior. This reduces the number of test cases while maintaining coverage.
Python Implementation: Generating Test Cases from User Stories
For teams using Python with pytest-bdd, here is the equivalent test generation approach.
# tests/generators/story_parser.pyfrom dataclasses import dataclass, field
import re
@dataclassclassInputParameter:
name: str
param_type: str# 'string', 'number', 'email', 'date'
constraints: list[str] = field(default_factory=list)
extracted_from: str = ""@dataclassclassParsedCriterion:
criterion_id: str
preconditions: list[str]
trigger: str
expected_outcome: str
business_rules: list[str]
input_parameters: list[InputParameter]
@dataclassclassParsedStory:
story_id: str
actor: str
action: str
benefit: str
criteria: list[ParsedCriterion]
implicit_requirements: list[str]
defparse_user_story(story: dict) -> ParsedStory:
"""Parse a user story dictionary into structured components."""
criteria = []
for ac in story.get("acceptance_criteria", []):
params = extract_input_parameters(ac.get("rules", []))
criteria.append(
ParsedCriterion(
criterion_id=ac["id"],
preconditions=[ac["given"]],
trigger=ac["when"],
expected_outcome=ac["then"],
business_rules=ac.get("rules", []),
input_parameters=params,
)
)
implicit = derive_implicit_requirements(story)
return ParsedStory(
story_id=story["id"],
actor=story["narrative"]["as_a"],
action=story["narrative"]["i_want"],
benefit=story["narrative"]["so_that"],
criteria=criteria,
implicit_requirements=implicit,
)
defextract_input_parameters(rules: list[str]) -> list[InputParameter]:
"""Extract input parameters and their constraints from business rules."""
params = []
for rule in rules:
# Pattern: "X must be Y-Z characters"
char_match = re.search(
r"(\w+)\s+must\s+be\s+(\d+)-(\d+)\s+characters", rule, re.IGNORECASE
)
if char_match:
params.append(
InputParameter(
name=char_match.group(1).lower(),
param_type="string",
constraints=[
f"min_length:{char_match.group(2)}",
f"max_length:{char_match.group(3)}",
],
extracted_from=rule,
)
)
# Pattern: "X must be between Y and Z"
range_match = re.search(
r"(\w+)\s+must\s+be\s+between\s+(\d+)\s+and\s+(\d+)", rule, re.IGNORECASE
)
if range_match:
params.append(
InputParameter(
name=range_match.group(1).lower(),
param_type="number",
constraints=[
f"min:{range_match.group(2)}",
f"max:{range_match.group(3)}",
],
extracted_from=rule,
)
)
return params
defderive_implicit_requirements(story: dict) -> list[str]:
"""Derive implicit requirements from story context."""
implicit = [
"All interactive elements must be keyboard accessible",
"Page must load within 3 seconds",
"Server errors must show user-friendly error message",
]
tags = story.get("tags", [])
if"authentication"in tags:
implicit.append("Authentication endpoints must have rate limiting")
return implicit
# tests/generators/gherkin_generator.pyfrom story_parser import ParsedStory, ParsedCriterion
defgenerate_feature_file(story: ParsedStory) -> str:
"""Generate a complete Gherkin feature file from a parsed story."""
lines = []
tag = story.story_id.replace(" ", "-")
lines.append(f"@{tag}")
lines.append(f"Feature: {story.action}")
lines.append(f" As a {story.actor}")
lines.append(f" I want {story.action}")
lines.append(f" So that {story.benefit}")
lines.append("")
for criterion in story.criteria:
# Positive scenario
lines.append(f" @{criterion.criterion_id} @positive")
lines.append(f" Scenario: {criterion.trigger} - happy path")
for pre in criterion.preconditions:
lines.append(f" Given {pre}")
lines.append(f" When {criterion.trigger}")
lines.append(f" Then {criterion.expected_outcome}")
for rule in criterion.business_rules:
lines.append(f" And {rule}")
lines.append("")
return"\n".join(lines)
Java Implementation: Generating Test Cases
For Java teams using Cucumber-JVM, the approach translates to the following structure.
Start with acceptance criteria, not implementation -- Generate test cases from the requirements as written, not from how you think the system works. This prevents tests that merely confirm existing behavior rather than validating intended behavior.
Generate negative scenarios for every positive path -- If the acceptance criterion says "user can log in with valid credentials," generate explicit scenarios for invalid credentials, expired accounts, locked accounts, and missing fields.
Use Scenario Outlines for data-driven tests -- When multiple equivalence classes test the same flow with different data, use Gherkin Scenario Outlines with Examples tables rather than duplicating scenarios.
Tag scenarios for selective execution -- Tag scenarios by priority (@P0, @P1), type (@positive, @negative, @boundary), and feature area (@auth, @cart). This enables targeted test runs in CI.
Review generated scenarios with business stakeholders -- Gherkin is readable by non-technical stakeholders. Use generated scenarios as a review artifact to validate that all acceptance criteria are covered.
Regenerate when requirements change -- When acceptance criteria are updated, re-run the generator to identify new test cases and flag obsolete ones. The traceability matrix makes change impact analysis straightforward.
Supplement generated tests with exploratory scenarios -- Generators cover systematic cases but miss creative edge cases. Augment generated suites with manually written scenarios discovered through exploratory testing.
Keep feature files focused -- One feature file per user story. Do not combine unrelated stories into a single feature file. This maintains the traceability link between stories and tests.
Validate Gherkin syntax before committing -- Use a Gherkin linter (cucumber-lint, gherkin-lint) to ensure generated feature files have valid syntax and consistent formatting.
Generate cross-cutting concern tests separately -- Security, performance, and accessibility tests that apply to all features should be in dedicated feature files, not scattered across individual story features.
Anti-Patterns to Avoid
Generating tests without reading the story -- Blindly applying templates without understanding the business context produces irrelevant test cases. Always read and parse the full user story narrative before generating.
Ignoring implicit requirements -- User stories rarely capture security, performance, and accessibility requirements explicitly. If you only generate tests for stated criteria, you miss critical coverage areas.
Over-generating trivial tests -- Not every equivalence class needs its own scenario. A password field with 56 boundary values does not need 56 separate scenarios. Use Scenario Outlines and focus on the most informative values.
Generating without prioritizing -- A flat list of 200 test cases with no priority is unusable. Every generated test must have a risk-based priority that determines execution order.
Treating generated tests as final -- Generated scenarios are a starting point, not a finished product. They need human review, refinement, and augmentation with domain-specific edge cases that no generator can anticipate.
Duplicating step definitions -- Generated step definitions should be reusable. "Given I am on the registration page" should be one step definition used across all scenarios, not duplicated in every feature file.
Ignoring the traceability matrix -- If you generate tests but do not maintain the traceability link to requirements, you lose the ability to assess coverage gaps and change impact.
Debugging Tips
Parser misses parameters: If the story parser fails to extract input parameters, check the phrasing of business rules. The parser expects specific patterns like "must be X-Y characters" or "must be between X and Y." Adjust regex patterns for your team's writing style.
Too many equivalence classes generated: If the generator produces an overwhelming number of classes, check whether it is generating redundant classes for overlapping constraints. Deduplicate classes with the same representative values.
Gherkin syntax errors in generated files: Ensure that quotes, special characters, and line breaks in acceptance criteria are properly escaped before inserting into Gherkin templates. Use a Gherkin parser to validate output.
Cucumber cannot find step definitions: Generated step definitions use exact string matching. If the Gherkin scenario uses "I submit a valid email and password" but the step definition expects "I submit valid email and password," the step will not match. Normalize articles and prepositions.
Traceability matrix shows low coverage: If coverage appears low, check whether the generator is correctly identifying all acceptance criteria from the source stories. Stories with non-standard formatting (missing Given/When/Then structure) may be partially parsed.
Priority calculator assigns everything as P1: If risk scores are uniformly high, recalibrate the weights and thresholds. Ensure that the business impact, failure likelihood, and complexity inputs vary across scenarios rather than defaulting to maximum values.
Generated feature files are too long: If a single feature file exceeds 200 lines, the source user story may be too large. Consider splitting the story into smaller stories with focused acceptance criteria before generating tests.
Step definition collisions: When multiple feature files generate similar step definitions, Cucumber may raise ambiguous step errors. Use parameterized steps with regular expressions to handle variations rather than creating nearly-identical literal steps.