Test case

On this page 7

A test case is a specified set of preconditions, inputs, actions and expected results that verifies one particular behavior of a piece of software. In software testing and programming, the test case is the smallest unit of verification that produces a clear verdict: the observed outcome either matches the expected outcome and the case passes, or it does not and the case fails.

The term covers both manual test cases, written as step-by-step instructions for a human tester, and automated test cases, written as code that a test runner executes. In both forms, a good test case is repeatable, independent and unambiguous, so that two people or two machines running it reach the same verdict.

What a test case consists of

Every test case, whatever its level, combines the same core elements: a known starting state, a stimulus, and a precise expectation. Formal standards such as ISO/IEC/IEEE 29119 describe these elements for documented testing, and automated tests express the same structure in code.

ElementPurposeTypical content
IdentifierMakes the case traceableID or descriptive test name
PreconditionsDefines the starting stateLogged-in user, seeded database row
Inputs and stepsDescribes the stimulusArguments, clicks, API requests
Expected resultDefines the verdictReturn value, status code, screen state
PostconditionsDescribes the state after the runRecord created, file removed
Traceability linkConnects to requirementsRequirement or user story reference
Elements of a well-formed test case

In automated code the same structure appears as the Arrange-Act-Assert pattern, also known as Given-When-Then in behavior-driven styles. The arrange step builds preconditions, often through a test fixture; the act step calls the code under test; the assert step compares the actual result with the expected result. Keeping these three phases visibly separate makes a test case far easier to read and to debug.

The expected result must be decided before the test runs, from a requirement or specification. A test that merely records whatever the code currently returns, without anyone judging whether that output is correct, confirms behavior but does not verify it. This is the central weakness of carelessly approved snapshot testing.

Types of test cases

Test cases are classified by what they target and how their inputs are chosen. The distinction matters because a balanced suite needs several kinds, not hundreds of variations of one happy path.

KindWhat it checksExample
Positive caseValid input gives correct outputCorrect login succeeds
Negative caseInvalid input is rejected safelyWrong password returns an error
Boundary caseBehavior at the edges of a rangeAge 17, 18 and 120 on an 18+ form
Edge caseUnusual but legal situationsEmpty list, leap day, Unicode name
Regression caseA fixed bug stays fixedReproduction of an old defect
Parameterized caseOne logic, many data rowsSame assertion over 20 inputs
Common kinds of test cases and their goals

Inputs are usually chosen with test design techniques. Equivalence partitioning groups inputs that the program should treat identically and tests one value per group. Boundary value analysis adds the values on and just beside each limit, where off-by-one errors cluster. Decision tables cover combinations of conditions, and state transition testing covers allowed and forbidden moves between states.

Property-based testing takes a different route: instead of fixed examples, the developer states a property that must hold for all inputs, and a framework generates hundreds of cases automatically, then shrinks any failing input to a minimal counterexample.

How a test case relates to neighboring concepts

A test case is a unit of specification, while several neighboring terms describe its level, its grouping or its supporting code. Confusing them leads to imprecise discussions about coverage and quality.

TermRelationship to a test caseScope
Test suiteA collection of test cases run togetherMany cases
Test fixtureShared setup that test cases rely onSupporting code or data
Unit testA test case at the level of one unitFunction or class
Integration testA test case across component boundariesSeveral modules
End-to-end testA test case through the whole systemUser journey
Test scenarioA high-level situation covered by several casesFeature or flow
Test case compared with related testing terms

In practice, a test scenario such as checkout with a discount code expands into multiple test cases: a valid code, an expired code, a code below the minimum basket value and a code applied twice. Each of those cases has its own expected result and its own verdict, which is what makes failures precisely diagnosable.

Writing effective test cases

An effective test case checks one behavior and fails for one reason. Experienced teams follow a small set of rules that keep cases trustworthy as a codebase grows from dozens to tens of thousands of tests.

  • Give each case a descriptive name that states the condition and the expectation, such as returns_404_when_user_is_missing.
  • Keep cases independent: no case may depend on another having run first, so the suite can run in any order or in parallel.
  • Make cases deterministic by controlling time, randomness and network calls, typically with a mock or stub.
  • Assert on observable behavior, not on private implementation details, so refactoring does not break correct tests.
  • Keep fast cases fast: unit-level cases usually run in milliseconds, while end-to-end cases may take several seconds or more.

In test-driven development (TDD), the test case is written first, observed to fail, and only then satisfied by production code. This order guarantees that each case can actually detect a missing behavior. Mutation testing checks the same property afterwards by introducing small code changes and confirming that at least one test case fails.

Common mistakes and how to fix them

Most problems with test cases are not missing tests but tests that cannot be trusted. The table lists frequent symptoms, their causes and the usual remedies.

SymptomLikely causeFix
Fails intermittentlyFlaky test with timing or shared stateControl clocks, isolate data, remove sleeps
Never failsMissing or weak assertionAssert the specific expected value
Breaks on every refactorCoupled to internalsTest the public behavior instead
Failure message is unclearSeveral behaviors in one caseSplit into focused cases
Passes locally, fails in CIEnvironment dependencyPin versions and seed data explicitly
Test case defects and remedies

High test coverage does not prove that test cases are good. A line can be executed without its result ever being checked, so coverage measures what ran, not what was verified. Coverage figures are best read alongside assertion quality and mutation scores.

The idea in brief

A test case is a small, written-down experiment. It says: start from this situation, do this exact thing, and expect this exact result. If the real result matches, the software passed that check; if not, something is wrong.

Think of a recipe card for checking a cake: oven at 180 degrees, bake for 30 minutes, and the cake should be golden and firm. Anyone following the card should reach the same conclusion. Software teams keep thousands of such cards, and computers run most of them automatically every time the code changes.

A practical scenario

A developer on an online store team must add a rule that orders above 50 euros ship for free. Before writing the logic, she writes four automated test cases: an order of 49.99 euros is charged shipping, an order of exactly 50.00 euros ships free, an order of 50.01 euros ships free, and an empty basket raises a validation error. These cases come directly from boundary value analysis around the 50-euro limit.

Her first implementation uses a strict greater-than comparison, and the case for exactly 50.00 euros fails immediately. She changes the comparison to greater-than-or-equal, all four cases pass, and the pipeline runs them on every later change. Months later, a refactoring of the pricing module accidentally reintroduces the old comparison, and the same test case catches the regression within minutes, before any customer is overcharged.