Turning Requirements Into Test Cases
Read a feature description, find what it does not say, and turn it into cases someone else can run: preconditions, steps, expected result, and one behaviour per case.
Read a feature description, find what it does not say, and turn it into cases someone else can run: preconditions, steps, expected result, and one behaviour per case.
The ticket said: "Users should be able to reset their password." Nine words, one acceptance criterion — "reset works" — and two weeks in production before anybody noticed that the reset links never expired. Not "expired after a week". Never. A link mailed in March still worked in September.
Nobody decided that. It was simply never asked, so the code answered it by accident.
By the end of this lesson you will know how to interrogate a requirement until its gaps show, and how to turn what you learn into test cases somebody else could run tomorrow and get the same answer you did.
Take that sentence and treat it as a pile of unanswered questions. "Users should be able to reset their password."
Ten questions, none of them exotic, every one of which will be decided by somebody. The only real choice is whether they get decided deliberately now, or accidentally in code later.
Look at question nine again. "What does an unregistered email address get told?" has a right answer — the same message a registered one gets, because anything else lets a stranger discover who has an account. That is a security requirement, and it fell out of asking what happens in the unhappy case.
You do not need a checklist of a hundred items. Four questions, run over any requirement, surface most of what is missing.
What is the exact rule?
Vague words hide decisions. Quickly, large, recent, valid, appropriate — each needs a number or a definition. "The link expires quickly" is not testable. "The link expires 60 minutes after it is sent" is.
What happens when it fails?
Requirements are written for success. What does the user see when the email cannot be sent, the token has expired, the account is locked, the network drops mid-request? Each is a behaviour somebody has to design — and if nobody designs it, the user gets a stack trace.
Who is allowed to do this?
Almost every feature has an authorisation dimension that goes unmentioned. Can a user reset another user's password? Can an admin? Should an admin be able to?
What else does this touch?
Features have consequences. Does resetting a password end active sessions, notify the user, appear in an audit log, invalidate API tokens?
You will come out with a list of questions. Take them to whoever owns the feature. That conversation is the testing work, and the answers become your test cases.
The answers should end up written down, in a form where "done" is not a matter of opinion. A common and effective shape is given / when / then:
Three properties make criteria like these worth writing. They are specific — 60 minutes, not "quickly". They are observable — somebody can look and see whether it happened. And they include the unhappy paths, which is where most of the value is.
Acceptance criteria say what should be true. A test case is instructions for finding out, precise enough that two people running it independently get the same result.
The parts:
Five things separate a case that is useful from a case that is noise.
One behaviour per case. If a case verifies four things and the second fails, what is the result? "Partly passed" is not a status. Split it.
Preconditions stated, not assumed. "A verified account exists" is the difference between a case anybody can run and one only you can.
Steps that need no guessing. "Log in" is ambiguous — as whom? "Log in as a user with no projects" is runnable.
An expected result specific enough to fail. "The page looks correct" cannot fail honestly. "The page shows the message This link has already been used" can.
Data that is real and written down. Not "enter an invalid
email" but "enter not-an-email". The next person should not
have to invent the input, because they will invent a different
one.
Bad — cannot fail, and cannot be reproduced:
Good — one behaviour, real data, an observable result:
The bad version is common because it is fast to write, and it does damage in three ways. Two testers running it test different things, so the result means nothing. It can never legitimately fail, because "correctly" is whatever the runner decides. And when a bug does slip through, nobody can tell whether the case covered it — so nobody knows whether the suite has a gap or the product has a defect.
The good version takes ninety seconds longer to write and stays useful for years.
Even one requirement generates more cases than are worth having, so pick deliberately. A sensible order:
Stop when the next case exercises the same code path as one you already have. Three invalid email formats reach one validation branch; a fourth costs time and adds nothing.
Detail should match how the case will be used. Over-formalising is a real failure mode, and an expensive one.
Almost nothing.
A note of what you tried and what happened. The exploratory testing lesson covers this mode properly.
The full form.
Different people will run it over years. Preconditions, exact data, exact expected result — everything the runner would otherwise have to guess.
The precision goes in the code.
Write an elaborate manual case and automate it and you now maintain two descriptions of one thing, which drift.
The trap is writing hundred-step scripts for everything. Detailed cases are expensive to write, expensive to keep current, and they go stale silently. A suite of a thousand stale cases is worse than fifty accurate ones, because it produces confidence nobody has earned and nobody knows which are which.
You can turn a requirement into cases. The next lesson makes step three of that priority list systematic: how to choose which values to test when the possible inputs never run out, using equivalence classes and boundary values. They give the best return of anything in this course.
Before that, take a real requirement from something you work on and run the four questions over it. The number of decisions nobody has made is usually a surprise, and finding them is the job.
Given a registered user with a verified email
When they request a password reset
Then an email is sent containing a single-use link
And the link expires 60 minutes after it is sent
Given a reset link that has already been used
When the user opens it again
Then they see "This link has already been used"
And they are offered a way to request a new one
Given an email address with no account
When a reset is requested for it
Then the same confirmation message is shown as for a real account
And no email is sentID TC-042
Title Reset link cannot be used a second time
Priority High
Precondition A verified account exists; a reset has been requested
and the link has already been used once
Steps 1. Open the same reset link again
Expected The page shows "This link has already been used"
with a "Request a new link" button
Actual (filled in when run)Title Test password reset
Steps 1. Reset the password
2. Check it works
Expected Password reset works correctlyTitle Expired reset link is rejected
Precondition A reset link was issued for alice@example.com more
than 60 minutes ago and has not been used
Steps 1. Open that link
Expected The page shows "This link has expired" and a
"Request a new link" button. No password is changed.# Four questions that find what the requirement left out
1. What is the exact rule?
vague words needing a number: quickly, large, recent, valid,
appropriate, soon, many
2. What happens when it fails?
expired, locked, offline, empty, unavailable, timed out
3. Who is allowed to do this?
the owner, another user, nobody logged in, an admin
4. What else does this touch?
sessions, notifications, audit logs, tokens, other users' data
# Acceptance criteria: specific, observable, unhappy paths included
Given a reset link that has already been used
When the user opens it again
Then they see "This link has already been used"
And they are offered a way to request a new one
# A test case that is worth having
ID a stable identifier
Title the one behaviour it checks
Priority so somebody can cut the list under pressure
Precondition the state required, stated not assumed
Steps unambiguous; real data, not "an invalid value"
Expected specific enough that it can honestly fail
Actual filled in when run
# Five rules
one behaviour per case "partly passed" is not a result
preconditions stated so anyone can run it
no guessing in the steps "log in as a user with no projects"
expected result can fail not "the page looks correct"
real, written-down data not "enter an invalid email"
# Order to write them in
1. the happy path, once
2. one failure case per rule
3. the boundaries of every limit
4. the authorisation cases
5. the already-done cases: twice, again, after a failure
6. the empty and first-time cases
stop when the next case exercises a path you already cover
# How much detail
run once, exploring almost none — notes
regression suite the full form; others will run it for years
about to be automated the precision belongs in the code, not both