· Obaid Sajjad
Prompt Engineering for Testers: Turning Requirements into Test Cases
- prompt-engineering
- test-design
- ai-testing
- llm
The prompt took thirty seconds to write. “You are a QA engineer. Here is the requirement: users can reset their password via email. Write ten test cases.” The model returned ten test cases. They looked reasonable, covered the happy path, and included a couple of obvious error states.
Then you read them carefully. There was nothing about what happens when the reset link expires. Nothing about whether a second reset request invalidates the first. Nothing about rate limiting, concurrent requests from the same account, or links clicked from a different browser or device. The list looked like coverage because it had ten items, and it had almost none of the coverage that matters.
The same model, given the same requirement, can produce a test set that misses every meaningful edge case or one that catches them systematically. The difference is not the model. It is the structure of the prompt. A throwaway prompt extracts whatever the model’s most probable response is. A reusable test-generation system shapes the output through structure, domain context, and staged reasoning.
This post is about the difference between the two and how to build the second one in an afternoon. The techniques here work across any LLM that can follow instructions, scale to any feature domain, and produce test cases that are genuinely ready for review rather than a list you have to fix before you can use.
Why Test-Generation Prompts Fail
Most test-generation prompts fail for one of three reasons, and the reasons are different enough that the fixes do not overlap.
The first is missing scope. The model does not know what it does not know. If you provide a one-sentence requirement, the model generates tests against that one sentence without any knowledge of the surrounding system: how the feature integrates with authentication, what the data model looks like, what the adjacent features are, what the known failure modes of similar features are in your codebase. The output is internally consistent with the input and externally incomplete relative to the actual system.
The second is the happy-path bias. Language models are trained on text that describes how things work, not how they fail. When asked to write test cases without explicit instruction to cover failure modes, the default output skews heavily toward positive scenarios. You get “user submits valid form, verifies email, logs in successfully” and very little about what happens when the form is submitted with an expired token, a previously used password, a non-existent email, or a malformed request.
The third is lack of structure in the output. A list of test cases with no consistent format, no severity indication, no grouping by behavior area, and no traceability to the requirement is hard to review and harder to maintain. If the output format is inconsistent, the reviewer has to normalize it before they can evaluate it, which wastes the time the prompt was supposed to save.
Each of these failure modes has a direct fix in the prompt design. Missing scope is addressed with context injection. Happy-path bias is addressed with explicit instructions to cover failure modes and boundary conditions. Output structure is addressed with a schema template that the model fills in rather than invents.
The Three Ingredients of a Good Test-Gen Prompt
A test-generation prompt that produces usable output has three ingredients, and leaving out any one of them degrades the result substantially.
The first ingredient is role and constraint. Tell the model who it is and what limits it operates under. “You are a senior QA engineer specializing in backend API testing. You write test cases that cover happy paths, error paths, boundary conditions, and security considerations. You do not write test cases for UI implementation details.” This is not decoration. It primes the model to apply a specific domain lens and to self-censor output that falls outside that lens.
The second ingredient is rich context. The requirement alone is never enough. Include: the feature’s integration points, the data model for the entities involved, the authentication context, any known constraints from adjacent features, and the definition of done for a complete test case in your team’s format. The more specific the context, the fewer gaps the model has to fill with generic assumptions.
The third ingredient is an explicit output schema. Tell the model exactly what each test case should contain, in what order, and in what format. A schema like “ID, Test Case Name, Preconditions, Steps, Expected Result, Priority (P1/P2/P3), Category (Happy Path / Error / Boundary / Security)” forces the model to reason through each dimension for each test case rather than writing prose that looks like test cases but is not structured enough to use directly.
Together, these three ingredients change the model’s task from “generate plausible test cases” to “fill in a structured template for a specific feature with the rigor of a senior QA engineer.” The output quality difference is significant.
Role, Context, Format: A Template
Here is a template that applies all three ingredients consistently. You fill in the placeholders for each new feature.
ROLE:
You are a senior QA engineer with expertise in [domain: API / mobile / web / data pipeline].
Your job is to produce complete, structured test cases that a developer could execute without
asking clarifying questions.
FEATURE CONTEXT:
Feature name: [name]
Requirement: [full requirement text, not a summary]
System integration: [what APIs, services, databases, or auth systems this feature touches]
Data model: [the key entities, their fields, and their constraints]
Adjacent features: [features that share state or interact with this one]
Known constraints: [rate limits, quotas, session rules, business rules]
OUTPUT SCHEMA (use this exact format for every test case):
ID: TC-[sequential number]
Name: [action + expected outcome, e.g. "Reset link rejected after 15-minute expiry"]
Preconditions: [state the system must be in before the test]
Steps: [numbered, one action per step]
Expected result: [specific, verifiable outcome]
Priority: P1 (blocks release) / P2 (should ship) / P3 (nice to have)
Category: Happy Path / Error / Boundary / Security / Performance
COVERAGE REQUIREMENTS:
Generate test cases in the following order:
1. All happy path scenarios
2. All invalid input and error path scenarios
3. Boundary condition scenarios (edge values, empty inputs, max lengths, zero quantities)
4. Security scenarios (unauthorized access, privilege escalation, injection, token reuse)
5. Concurrency scenarios if the feature involves shared state
Do not stop until all five categories have at least two test cases each.
The “do not stop until” instruction at the end is not redundant. Without it, models frequently produce a strong happy path set and thin coverage in the other categories. The explicit minimum forces balance.
Chaining Prompts: From Requirements to Test Cases
A single prompt that goes straight from requirement to test cases produces adequate output for simple features. For complex features, a two-stage chain produces substantially better results: one prompt for boundary analysis, one prompt for test case generation using the boundary analysis as input.
Stage one is the boundary analysis prompt. You send the requirement and ask the model to enumerate: all the input dimensions (fields, parameters, headers, state), the valid range for each dimension, the boundary values at the edges of each range, and the invalid inputs that should trigger error handling. This stage does not produce test cases. It produces a structured map of the testing surface.
A boundary analysis for a password reset feature might produce:
Input dimensions:
- Email address: valid format / invalid format / non-existent / previously deactivated account
- Reset link: valid / expired (>15 min) / already used / modified token / from different IP
- New password: meets complexity / too short / too long / same as current / common password
- Request timing: first request / second request within window / request after expiry
- Concurrent: single request / two simultaneous requests from same account
Stage two sends this boundary map to the test case generation prompt instead of the raw requirement. The model now generates test cases against a complete, explicit surface rather than inferring the surface itself. The two-stage approach also makes the boundary map a reviewable artifact: you can read it and catch omissions before any test cases are written, which is faster and cheaper than reviewing a test case set for gaps.
Reusable Prompt Templates
The investment in writing a good prompt template pays back across every feature you test. A template built for authentication feature testing works for login, registration, password reset, session management, and token refresh. A template built for data validation covers every form and every API input. The domain context changes; the structure and the coverage requirements stay the same.
Treat prompt templates as version-controlled artifacts alongside your test code. Store them in a prompts/ directory in your test repository, name them by domain, and update them when you discover a gap. A test case the prompt failed to generate for the first feature should produce a template update so the second feature gets better coverage automatically.
One pattern worth building: a coverage checklist prompt that runs after the test case set is generated. You send the generated test cases back to the model with an instruction to identify any category from the coverage requirements that has fewer than two test cases, and to explain which scenarios within that category are missing. This acts as a self-review step that catches thin coverage before the set goes to a human reviewer.
Review the following test case set for coverage gaps.
For each of these categories, confirm whether there are at least two test cases,
and list any specific scenarios within the category that are missing:
[Happy Path / Error / Boundary / Security / Concurrency]
Test cases: [generated set]
The output of this review prompt is a gap list you can either address with additional prompting or hand off to a human as known gaps with context.
Reviewing LLM-Generated Test Cases
No matter how well-structured your prompts are, LLM-generated test cases need human review before they go into the suite. The model is not wrong often, but when it is wrong it tends to be wrong in specific ways that a reviewer can learn to spot quickly.
Watch for steps that assume implicit state. A test case that says “click the reset link” without specifying how the link was generated, which email account it was sent to, or what browser state is assumed is not executable without clarification. Preconditions should leave nothing for the executor to infer.
Watch for expected results that are too vague. “User sees an error message” is not a verifiable outcome. “User sees the error message ‘This link has expired. Request a new one.’ and the link is no longer valid for subsequent use” is. Vague expected results make it impossible to determine whether a test passed.
Watch for test cases that test the UI instead of the behavior. A test that says “user sees the green checkmark icon” is testing a visual implementation detail. A test that says “the system returns HTTP 200 and the response body contains {success: true}” is testing the behavior. For backend and API features, the latter is almost always what you want.
The review step for a well-prompted test set should take ten to fifteen minutes for a twenty-case output. Most of it will be verifying that preconditions are complete and expected results are specific. The model will have covered the surface well; your job is to sharpen the definitions.
From Throwaway to System
A throwaway prompt generates tests. A reusable system generates tests, reviews them for coverage gaps, flags weak expected results, and produces a traceable artifact that a developer and a reviewer can both use without additional work.
The difference in effort is a few hours up front to build the template, the boundary analysis stage, and the coverage review prompt. After that, a new feature takes the same thirty minutes it took before, but produces a test set that actually covers the edge cases that matter.
The most expensive part of test design is not writing the test cases. It is knowing which cases to write. A well-structured prompt externalizes that knowledge into a reusable artifact, which means it compounds: every feature you test with the system improves the template, and every improvement makes the next feature’s coverage better. That compounding is what makes prompt engineering a worthwhile investment for a QA team, not just a way to save an afternoon.
Start with the template in this post, run it on a feature you know well, and compare the output to the test cases you would have written by hand. The gaps in the prompt output tell you what context to add. The gaps you would have missed by hand tell you why the boundary analysis stage is worth the extra step. That comparison is the fastest way to learn what makes a test-generation prompt worth building.