How to write test cases developers actually read
Most test cases are written for an audit nobody runs. Here is the format I use to write ones developers actually read — and act on.
part ofManual QA that still mattersI spent my first two years writing test cases the way the template told me to: a numbered list of ten steps, one cell per step, an "Expected Result" column I filled with "User is able to login successfully." Nobody read them. Not the developers, not the next tester, and — if I'm honest — not me, two sprints later. They existed to be counted, not to be used.
A test case is a message to another person: "here is a thing worth checking, and here is how you'd know it broke." If that message doesn't survive contact with a busy developer at 4pm, the format is wrong. Here's the format that does.
Why developers don't read your test cases
It's not laziness. It's cost. A developer opens your case because something failed and they want to reproduce it. Every word between them and the reproduction is a tax. The classic ten-step case charges that tax on every line:
- Navigate to the home page
- Click the "Login" button in the top right
- Observe the login form appears
- Enter a valid email address in the email field
- Enter a valid password in the password field
- Click "Sign in"
- ...
By step three they've stopped reading, because steps one to three are the same on every case ever written and carry no information. The signal — what's actually being checked here — is buried. Good test cases front-load the signal and compress the noise.
Write the title as the assertion
The title is the only part guaranteed to be read. Make it the claim you're verifying, not the area you're poking at.
- ❌ "Login page"
- ❌ "Verify login functionality"
- ✅ "Logging in with a valid email but wrong password shows an inline error, not a redirect"
The good title tells a developer, in one line, exactly what behaviour broke if this case is red. They can often fix the bug without opening the steps at all. That is the goal — the steps are the fallback, not the payload.
One behaviour per case
If your title needs an "and", split it. A case that checks "valid login works and invalid login is rejected and the account locks after five tries" can't have a meaningful status — when it fails, which behaviour failed? You're back to reading ten steps to find out.
One behaviour per case means each red result points at exactly one broken thing. It also means your suite reads like a specification: a scannable list of every claim the feature makes about itself.
Preconditions are setup, not prose
Everything true before the interesting part starts goes in a preconditions block, stated as facts, not steps:
Preconditions: A registered user
qa-demo@example.comexists with a known password. The account is not locked.
This is the single highest-leverage change. All those "navigate to home, click login" steps were never the test — they were getting to the test. Demote them to setup and the actual behaviour stands alone.
Steps a developer can skim
With setup factored out, the steps are just the behaviour, in the present tense, one action per line. Data goes inline so there's nothing to cross-reference:
- Submit the login form with
qa-demo@example.com/wrong-password- Read the response
Two steps. A developer skims it in a second. Compare that to the eleven-line version — same coverage, a tenth of the tax.
The expected result is specific or it's worthless
"User is able to login successfully" is not an expected result; it's a restatement of the title. A real expected result is something a person — or a machine — can disagree with:
Expected: The form stays on
/login. An inline error reads "Email or password is incorrect" (note: not "wrong password" — we don't confirm the email exists; that's an account enumeration bug). No redirect occurs. No session cookie is set.
Notice it specifies what should happen and pins down the things that are easy to get subtly wrong — the generic error wording, the absent cookie. That's where the real bugs live, and a vague expected result waves them straight through.
Before and after
Here's the same case, both ways.
Before (11 rows, unread):
Title: Login functionality Steps: navigate to site → click login → form appears → enter email → enter password → click sign in → ... Expected: User logs in successfully
After (one screen, used):
Title: Logging in with a valid email but wrong password shows an inline error, not a redirect Preconditions:
qa-demo@example.comexists, not locked. Steps: Submit the login form withqa-demo@example.com/wrong-password. Expected: Stays on/login; inline error "Email or password is incorrect"; no redirect; no session cookie set.
The second one is shorter, but it isn't thinner — it carries more real information, because every word earns its place.
A note on tools and templates
This format is tool-agnostic — it works in a spreadsheet, in Jira, in a markdown file. If you want a starting point, the test case template follows this structure, and the test design techniques reference covers how to choose which cases are worth writing in the first place (this post is about writing them well, not picking them).
Before you save a test case
- The title is the assertion being verified, readable on its own
- It checks exactly one behaviour (no "and")
- Setup lives in preconditions, not in the steps
- Steps are present-tense, one action each, with data inline
- The expected result is specific enough to disagree with
- It pins down the subtle things (exact wording, cookies, redirects) that bugs hide behind
// RELATED QA.CODES RESOURCES
Checklist
Template
// related
How to report bugs developers can fix quickly
A bug report exists to get the bug fixed. Specific title, minimal repro steps, explicit expected-vs-actual, evidence, and environment — the format that prevents "can't reproduce".
Regression testing without wasting two days
How to scope a regression pass to the change in front of you instead of re-running the entire suite by hand.