What Is a Test Case? Anatomy, Types, and How to Write One
A test case is a documented set of conditions, inputs, and steps used to verify that one specific feature or behavior of a software product works as intended. Each test case pairs an expected result with the actual result, so a tester or an automated runner can mark it pass or fail. Good test cases are atomic — they check one behavior each — repeatable, and specific enough that anyone can run them and get the same outcome.
What are the parts of a test case?
A complete test case has a predictable anatomy. Every field earns its place by removing ambiguity about what is being tested and whether it passed:
- ID and title — a stable identifier and a one-line summary of the behavior under test (for example, "Login rejects an unregistered email").
- Preconditions — the state the system must be in before you start: logged-out session, a seeded test account, a specific feature flag on.
- Test data — the exact inputs you will use, not "a valid email" but the specific value, so the run is reproducible.
- Steps — numbered, single-action instructions in the order a user performs them.
- Expected result — the one observable outcome that means "pass," written before the run.
- Actual result and status — what actually happened, and pass/fail/blocked. This is the field that turns into a bug report when it fails.
The discipline that matters most: write the expected result before you execute the case. Filling it in afterward turns the test into a description of whatever the software already does — bugs included.
Test case vs. test scenario vs. test script
These three terms get used interchangeably, but they sit at different levels of detail:
- A test scenario is a high-level goal to verify: "a user can recover a forgotten password."
- A test case is one concrete path through that scenario with specific data and a single expected result: "submitting an unregistered email shows the same neutral confirmation message and sends no email."
- A test script is the executable version — code or a recorded flow that runs the case automatically.
One scenario fans out into several test cases; each test case may become one test script. Confusing the levels is why some teams have "100 test cases" that are really 10 vague scenarios, and why others automate scripts nobody can trace back to a requirement.
Types of test cases you should be writing
Coverage comes from testing distinct kinds of behavior, not from volume. The types that catch the most real bugs:
- Positive (happy path) — valid input produces the intended result. This is the baseline; if it fails, nothing else matters.
- Negative — invalid or unexpected input is rejected gracefully, with a clear error and no crash or data corruption. Most escaped defects hide here.
- Boundary value — the edges of an allowed range: 0, 1, the maximum, the maximum plus one, an empty field, a field at its character limit. Off-by-one bugs live at boundaries.
- Equivalence partition — one representative case per group of inputs that should behave identically, so you get coverage without testing every possible value.
- State transition — the system behaves correctly moving between states (draft to published, trial to paid, logged-in to session-expired).
How to write a good test case, step by step
- Name the single behavior. If your title needs the word "and," split it into two cases. One behavior per case keeps a failure diagnostic.
- State the precondition and exact data. Replace every "a valid X" with the real value you will use. Reproducibility is the whole point.
- Write numbered, single-action steps. Each step should be one thing a person does, in order, with no branching.
- Define one unambiguous expected result. Written up front, phrased so two people could not disagree on whether it passed.
- Decide what evidence a failure needs. For a UI case, that's a screenshot plus the console and network log at the moment of failure; for an API case, the request and full response. Deciding this in advance is what makes a failed case immediately actionable.
Common test case mistakes to avoid
- Vague inputs. "Enter some text" is not a test case — it can't be reproduced or automated.
- Bundled behaviors. A case that verifies login and the dashboard and a notification tells you something broke but not what.
- Expected result written after the run. This bakes existing bugs in as "correct."
- No failure evidence plan. A case that fails with only "didn't work" recorded creates a clarification round-trip instead of a fix.
From test case to automated test and bug report
A well-structured test case is already most of the way to two other artifacts. Because its steps and expected result are explicit, it converts cleanly into an automated end-to-end test — and tools that keep those tests running as your UI changes save you from rewriting selectors every release (that's the idea behind self-healing e2e tests). And when a case fails, the actual result plus the evidence you planned for becomes a complete bug report on its own; capturing that evidence at the moment of failure — screenshot, console, and network in one click — is exactly what in-app bug reporting is for. Write the test case well once, and the automated test and the bug report both fall out of it. For more QA fundamentals, browse the Klavity blog.
Key takeaways
- Keep every test case atomic — one behavior, one expected result
- Write the expected result before you run the case, never after
- Cover the happy path, key error paths, and boundary values
- Capture actual result plus evidence when a case fails, so triage is instant
FAQ
What is the difference between a test case and a test scenario?
A test scenario is a high-level thing to test ('user can reset a forgotten password'). A test case is the concrete, step-by-step way to verify part of that scenario, with specific inputs and one expected result. One scenario usually expands into several test cases — the valid email, the unregistered email, the expired reset link, and so on.
Should test cases be manual or automated?
Automate the stable, repetitive, high-value cases you run every release — logins, checkout, core API responses. Keep exploratory and one-off checks manual, since scripting them costs more than they return. A well-written manual test case is also the fastest thing to hand to an automation tool later, because the steps and expected result are already explicit.
How many test cases do I need for a feature?
Enough to cover the happy path, the main error paths, and the boundaries — not one per imaginable input. Coverage comes from testing distinct behaviors and equivalence classes, not from sheer count. Ten sharp cases usually beat fifty overlapping ones that all re-check the same code path.
What makes a test case a good one?
It is atomic (verifies one behavior), repeatable (anyone running it gets the same result), and specific (exact inputs and an unambiguous expected result). If two people could disagree on whether it passed, it is not specific enough yet.
Catch bugs the moment a human sees them
Klavity: right-click bug reports, AI personas that review your product, and self-healing tests.
Get started free