You already know how to write a test case: title, preconditions, steps, expected result. But a properly formatted case can still be a poor one — checking the wrong thing, the wrong way, or so tediously that nobody executes it as intended. This article is about the next level: what separates a case that actually catches bugs from a case "for the record."

The balance of specificity and generality

Compare two cases for the same photo upload check:

  • Case A: "Open C:/test/photo1.jpg of exactly 2,371,008 bytes, upload to the album 'Vacation-2026'…"
  • Case B: "Upload a photo to an album."

Both are bad — for opposite reasons.

Too specific (A) executes exactly the same actions with exactly the same data on every run — meaning run after run it probes the same single point. The chance of finding a new error drops: everything around that point stays unchecked. It also takes longer to write, read, and maintain.

Too general (B) will be understood differently by every executor — who will effectively run a different case, not the one the author intended. A beginner will simply freeze in front of it.

The golden middle: "Upload a photo of an allowed format and size, with spaces and non-Latin characters in the file name." Everything needed for execution is there, while the concrete values will vary slightly from run to run — and that's a plus: data variability increases the chance of snagging an error, especially when different people execute the case.

Fault-finding power: a case must be able to find an error

A case is better the higher the probability that it uncovers a problem. Compare:

  • "Start the application. Expected result: the application starts" — a weak case. This action will happen hundreds of times during any work with the product anyway; a dedicated check adds almost nothing.
  • "Start the application with the working folder path pointing to a nonexistent drive. Expected result: a message explaining the problem, graceful termination" — a strong one: it checks a situation where an error is likely, and which nobody will hit by accident.

Strong cases perform "interesting actions": saving a file to a full disk, submitting a form with an expired session, entering a name made of spaces only. Trivial scenarios get covered in passing anyway. If your whole suite consists of "open — look — close," it creates the appearance of testing, not testing.

Wording rules

Craft details that distinguish a tidy case:

  • Imperative infinitive form: "open," "enter," "click" — consistent, neutral instructions rather than personal commands.
  • Present tense in expected results: "the entered value is displayed in the field" — not "was displayed" or "will be displayed."
  • Element names, not descriptions: "the system button 'Close'," not "the little cross at the top right"; "double click," not "press the left mouse button twice very fast."
  • No "verify that" in checklists: the whole checklist is already a list of checks. "Application startup" instead of "Verify application startup," "division by zero" instead of "attempt to divide by zero" — shorter and more informative.
  • Never reference other cases or their steps. That other case will be changed or deleted — and yours silently breaks. Everything you need goes into your own preconditions.

Two iron rules

The expected result always describes the application working correctly. There is no such thing as a case whose expected result is "the application crashes with data loss" — even in the harshest negative scenario. Correct behavior may well mean an error message: "Cannot save the file: not enough free space" is not a bug — it's the right behavior. The bug would be the absence of that message, or lost data.

A problem in the preparations is not an application bug. Everything described in the preconditions is prepared without the product under test. If the environment couldn't be set up (no test user, server unavailable) — that blocks the case, but you cannot file a bug against the application: you haven't started checking it yet. Like a tasting: if the store was closed and you couldn't buy the sweets, that doesn't mean the sweets taste bad.

Typical logical mistakes

  • Inventing behavior. The requirements say "the default folder is displayed," but which one exactly is written nowhere. You can't put "C:/SavedDocuments is displayed" into the expected result out of your head — you need to ask a question. A case built on a guess checks your imagination, not the product.
  • Detail at the wrong level. A smoke suite is no place for checking every button, and a complex rare scenario is no place in smoke. Cases should evolve from level to level ("application startup" → "startup from the command line, via shortcut, from the menu"), not be copied verbatim.
  • Cases about the wrong application. We're testing a photo gallery on a website — and the suite contains "file from a network drive," "file locked by another application." Those cases test the browser and the operating system, not our product. Ask yourself: whose code can break here?
  • Checking standard system functionality. Window minimizing, system buttons, scrolling — unless your product does something special with them, the chance of finding an error there is near zero, while the time spent on the cases is real.

Where this applies

These criteria are your tool twice over. When writing your own cases, run down the list: is the case strong, is it too specific, is the behavior invented? When executing someone else's — notice the problems and propose fixes: refining the suite is a normal part of the job; suites live and evolve.

Where beginners stumble:

  • They chase quantity. Fifty weak "open-close" cases are worse than ten strong ones. The metric "how many cases written" without asking "what do they catch" is empty.
  • They nail down every value. A case with hard-coded data probes the same point run after run. Leave meaningful freedom.
  • They write the expected result as "the application shows an error" — without saying which one and where. Tomorrow the application crashes with a system error, and the executor honestly marks "passed."

What to learn next. Test design techniques help you invent strong checks systematically — boundaries and equivalence classes point exactly at the "interesting" spots. And how to link cases into chains that resemble real user behavior — in the article about user scenarios.