Resource library

QA How-To

Test Case Writing Best Practices with Examples

Write clear, risk-based test cases with strong preconditions, precise steps, measurable expected results, boundary coverage, and practical examples.

1,928 words | Article schema | FAQ schema | Breadcrumb schema

Overview

A useful test case is a compact agreement about behavior. It tells another tester what condition to establish, what action to take, and what observable result proves the requirement. It is specific enough to reproduce a failure but not so burdened with obvious clicks that every minor layout change requires a rewrite. That balance is a design skill, not clerical documentation.

This guide shows how to move from requirements and risk to an executable set of cases. You will use a practical account-registration feature, improve a weak example, apply boundary and decision techniques, separate data from steps, and review coverage without counting cases for its own sake. The same principles work in a spreadsheet, a test management platform, behavior specifications, or test code because clarity and traceability do not depend on a particular tool.

Start With Purpose and Risk

Before writing steps, identify what could go wrong and why it matters. For registration, major risks include creating an unusable account, accepting a duplicate identity, exposing whether an email exists, losing consent evidence, and failing to send verification. These risks deserve stronger cases than cosmetic spacing on a secondary hint. Requirements describe intended behavior, while risk helps decide how deeply to test it.

Clarify ambiguous rules with product and engineering. Is email comparison case insensitive? Does a password allow spaces? How long is a verification link valid? What happens when delivery fails? Record answers in acceptance criteria or linked notes. A test case should not silently invent expected behavior. If a decision is pending, label the case blocked by that question rather than converting an assumption into an apparent product contract.

Use a Consistent Test Case Anatomy

A practical record includes a unique ID, behavior-oriented title, requirement or risk link, priority, preconditions, test data, steps, expected results, and cleanup if needed. Execution fields such as environment, build, actual result, status, and evidence belong to a particular run rather than the reusable design. Keep authorship and revision history only when the management system does not already track them.

The title should identify condition and outcome: `Registration rejects an email already assigned to an active account`. Preconditions might state that an active account exists for a named test email and the registration page is available. The expected result should include the rejection, absence of a second account, and safe user message. This is much more searchable and informative than `Check invalid registration` or `Test case 17`.

  • Use one stable ID even if the title later improves.
  • Link a requirement, risk, defect, or acceptance criterion.
  • State preconditions that are not created by the steps.
  • Keep reusable data separate from execution evidence.
  • Include cleanup when the test changes persistent state.

Write Actions and Results at the Right Level

Each numbered step should contain one meaningful action, paired with an observable expected result when verification belongs at that point. For example: enter a registered email, enter a valid new password, accept required terms, and submit. The final result should state that registration is rejected, the page retains safe fields according to policy, the message does not reveal sensitive account status beyond the approved wording, and no new user record or email is created.

Avoid excessive navigation detail such as click the browser icon, place the cursor in the address bar, and press Enter. It obscures the behavior and makes the case expensive to maintain. At the other extreme, `test duplicate email` is not executable by a new teammate. Describe product interactions and required observations. Put common environment setup in a referenced precondition or reusable procedure, while keeping the scenario-specific path visible.

Turn a Weak Case Into a Strong One

Weak example: Title, `Test signup`. Step, `Enter details and click submit`. Expected, `User should not register`. It omits which data is invalid, the existing state, the expected message, and whether data persists. Two testers could run entirely different scenarios and both claim completion. A developer receiving a failure would have to ask for every useful detail. The case also cannot be reviewed against a specific rule because its intended coverage is unknown.

Improved example: Title, `Active account email cannot be registered again`. Preconditions, active user `existing@example.test`; outbound email capture is empty. Data, same email plus a password meeting policy. Steps, open registration, enter the data, accept required terms, submit. Expected, remain unauthenticated, display the approved generic response, create no user, and send no second verification email. Cleanup, none. This case is narrow, reproducible, and checks the security-sensitive side effects.

Use Equivalence Partitions and Boundaries

Do not create one case for every possible value. Divide inputs into groups expected to behave alike, then choose representatives. If display name accepts 2 through 50 characters, partitions include below minimum, valid, and above maximum. Boundary cases should cover 1, 2, 3, 49, 50, and 51 characters because comparison defects cluster where acceptance changes. Include the counting rule, especially when spaces, emoji, or composed Unicode characters may count differently.

Apply partitions to format as well as length. Email cases can represent a normal address, mixed case, subdomain, missing local part, missing domain, spaces, Unicode according to the stated policy, and an address already registered. Test explicit null, absent field, and empty string separately at the API layer because serializers and validators may treat them differently. The goal is a defensible sample of behaviors, not an impressive row count.

  • Test just below, at, and just above every meaningful limit.
  • Choose one or two representatives from same-behavior classes.
  • Separate format, length, uniqueness, and required-field rules.
  • Include normalization cases such as leading spaces and letter case.
  • Reduce duplicate cases that exercise the same rule through the same path.

Use Decisions and States for Business Logic

A decision table handles combinations better than an unstructured list. Suppose verification email behavior depends on account status and link age. List conditions such as account unverified, link within 24 hours, and link already used. Define outcomes for each valid combination: activate account, show expired and offer resend, or show already verified. Remove impossible combinations only after confirming why they cannot occur.

State transition testing covers sequences where history matters. An account might move from unverified to active after a valid link, from unverified to expired after time passes, and from active to suspended after an administrative action. Write cases for valid transitions and attempts that must be rejected, such as reusing a verification token. These techniques prevent a common documentation weakness where every case begins from a clean page and ignores the lifecycle that produces serious defects.

Keep Data Specific, Safe, and Reusable

Put exact data in a table or dedicated field instead of hiding it inside prose. Include user role, locale, input values, account state, and identifiers necessary to reproduce the result. Use clearly synthetic domains and non-sensitive values. Never copy production customer records into test cases, screenshots, or shared environments. If a password is required, reference a secret fixture or policy-compliant placeholder rather than publishing a credential.

Parameterized cases work when steps and expected behavior remain identical across a concise data set. Do not merge five inputs into one row if a failure would not reveal which value broke. For mutable records, specify whether the runner creates a unique user or resets a seeded account. Data ownership is part of repeatability, especially when cases run concurrently or several testers share an environment.

Make Expected Results Observable and Measurable

Avoid `works correctly`, `loads fast`, and `error is shown`. State what a user or connected system can observe. A better result is `A confirmation page displays the masked email, the account remains unverified, and one verification message is queued within the documented service window`. If performance matters, link the actual threshold and measurement method rather than asking a manual tester to judge speed by feeling.

Check side effects and absence of side effects. A rejected registration should not create a partial profile, consume an invitation, emit a success event, or send a welcome email. When the UI cannot observe the important result, pair the case with an API, database, log, or event check available in the test environment. Specify which source is authoritative so different runners do not use incompatible evidence.

Balance Independence With End-to-End Workflows

Most reusable cases should establish their own preconditions and leave the environment usable afterward. Do not require TC-104 to pass before TC-105 can execute unless the sequence itself is what you are validating. Dependencies make triage difficult and prevent selective execution. If setup is expensive, use a known seeded state or an API fixture rather than relying on an earlier manual case.

Some workflows legitimately span several steps, such as register, verify, sign in, and delete the account. Document them as one scenario with checkpoints or as a clearly named scenario suite whose orchestration creates fresh data. Do not fragment a single user journey merely to increase case count. The unit of documentation should make failures precise while preserving the business behavior under test.

Review, Prioritize, and Maintain the Suite

Peer review test cases before execution for high-risk features. Ask whether the expected result follows an approved rule, boundary coverage is present, permissions and error paths are considered, data can be created, and a stranger can reproduce the case. Map cases to requirements and risks, then inspect uncovered high-impact areas. One requirement can need many cases, and one well-designed workflow can cover several criteria, so traceability is not a one-to-one counting exercise.

Assign priority from business impact, change likelihood, and usage. Mark a small stable smoke set, then broader regression groups. After defects and production incidents, add or refine the case that would have detected the issue at the cheapest layer. Remove obsolete duplicates and update cases when behavior intentionally changes. Versioned, reviewed documentation is a living test asset. An enormous stale repository creates false confidence and slows every release.

Frequently Asked Questions

What fields should a test case include?

Include an ID, descriptive title, requirement or risk link, priority, preconditions, test data, steps, expected results, and cleanup when needed. Environment, build, actual result, status, and evidence belong to each execution record.

How detailed should test case steps be?

Describe meaningful product actions so another qualified tester can execute the case without guessing. Omit routine computer operations and shared navigation details, but include exact scenario choices, data, and checkpoints that affect the result.

What makes a good expected result?

It is specific, observable, and based on an approved requirement. It describes visible behavior and important side effects, including what must not happen after a rejected or failed action.

How many test cases should I write for a feature?

There is no correct fixed number. Use risk and test design techniques to cover distinct behaviors, boundaries, roles, states, and failure paths. Remove cases that repeat the same rule without adding detection value.

Should positive and negative tests be separate cases?

Usually yes when they need different data, preconditions, or expected results. Separate cases make execution status and failures precise. A concise data-driven case can combine values only when the workflow and expectation are truly identical.

How often should test cases be updated?

Review them whenever requirements, workflows, integrations, or risks change, and after escaped defects reveal a gap. Regularly archive obsolete and duplicate cases so the active suite stays trustworthy.

Related QAJobFit Guides