TestForge

Writing test cases people can actually run

Anatomy of a good case, the five failure modes of bad ones, and how much detail is the right amount.

13 minQA FundamentalsHands-on

This lesson has an exercise. It runs in your Academy sandbox — a real TestForge project seeded with ShopMini, kept out of your dashboard and projects list.

The one test that matters

Could a competent person who has never seen this feature run your case and get the same verdict you would? If yes, it's a good test case. Everything below is in service of that.

Anatomy

PartWhat it's forExample
TitleFindable, and tells a reader what's covered without opening itCheckout — quantity above maximum (100) is rejected
PreconditionsThe state the world must be in before step 1Logged in as a customer; cart contains 1 × "Kaos Polos"; stock ≥ 200
Test dataConcrete values, not descriptionsquantity = 100
StepsNumbered actions, one action each1. Open the cart. 2. Set quantity to 100. 3. Click Update.
Expected resultObservable, specific, one per step where it mattersQuantity stays 99; inline error "Maximum 99 per order"; cart total unchanged
PriorityWhat runs first when time runs outHigh

TestForge gives each of these a field, and steps are action/expected pairs — so the structure above is the form you'll be filling in.

Titles: the part everyone rushes

A title is read a hundred times and written once. Use a shape and stick to it:

[Area] — [condition] → [expected outcome]

  • Checkout — quantity above maximum (100) is rejected
  • Login — locked account with correct password shows generic error
  • Test quantity — covers what? passes when?
  • Verify that the system works correctly — nothing at all
  • TC-17 — the ID is not a title

The test: read only the title and predict the expected result. If you can't, rewrite it.

Expected results must be observable

"Works correctly", "as expected", "the system behaves properly" are not expected results — they're a promise to argue later. Write what a person can see:

  • Order is processed correctly
  • Order status becomes "Paid"; confirmation email arrives at the customer's address within 1 minute; stock for SKU-1042 drops from 200 to 198

If the expected result can't be observed from the outside, say where to look — a database row, a log line, a webhook payload. That's still observable.

The five ways test cases go bad

1. Too vague. "Enter an invalid email." Which invalid email? a@b, no-at-sign, x@x., 300 characters, unicode? Each is a different partition and they don't behave the same.

2. Too detailed. Twelve steps to log in, described click by click, in every case. Put shared setup in preconditions, or in a shared step group, and start the case at the thing it's actually testing. A case should be ~3–8 steps.

3. Testing five things at once. A case that checks login, then the dashboard, then the profile page fails on step 2 and tells you nothing about the rest. One case, one reason to fail.

4. Depends on the last test's leftovers. Case 12 passes only if case 11 ran first and left the cart full. Someone runs case 12 alone, it fails, and everyone wastes an hour. State the precondition, or make the case set itself up.

5. Encodes the implementation. "Click the button with class .btn-primary at the top right." When the design changes, the case is wrong but the software is fine. Describe intent — "Confirm the order" — not markup.

How much detail is right?

It depends on who runs it and how often, and there is a real trade-off:

SituationStyle
You'll run it once, today, yourselfA charter or a checklist line. Don't gold-plate it
A new joiner will run itFull steps, explicit data
It's a regression case run every releaseFull steps — it will outlive you
It's regulated / auditableFull steps, plus evidence of the run
You'll automate it next sprintPrecise data and assertions; skip the UI choreography

The failure mode of junior QA is writing 200 exhaustively detailed cases nobody maintains. Detail costs maintenance. Spend it where a case will be re-run by someone who isn't you.

Worked example

Requirement. ShopMini cart: quantity 1–99 per line item.

Bad:

Title: Quantity test Steps: Test the quantity field with different values Expected: Works correctly

Good — and note it's one partition, with its own case:

Title: Cart — quantity above maximum (100) is rejected Priority: High Preconditions: Logged in as customer buyer@shopmini.test; cart contains 1 × "Kaos Polos" (SKU-1042); stock ≥ 200 Steps:

  1. Open /cart → the line item shows quantity 1
  2. Type 100 into the quantity field for SKU-1042
  3. Click Update cart

Expected result: Quantity reverts to 99 (or stays at 1 and is not applied); inline error "Maximum 99 per order" is shown next to the field; cart subtotal is unchanged; no request is sent to the order service.

Its siblings — 0, 1, 99, 2.5, "abc", empty — are separate cases, from your partition and boundary work in the previous lessons.

🛠 Your turn, in TestForge

The sandbox exercise: create the case above (and its boundary siblings) in a real ShopMini project, with real steps and expected results. The checker looks for a case in the Checkout suite with at least three steps, a non-empty expected result, and boundary values in the data — the same standard a reviewer would apply.

Next: the other thing you'll write every day — a defect report that gets fixed instead of closed as "cannot reproduce".

Check your understanding

3 questions. No account needed, nothing is sent anywhere but the grader.

  1. 1. Which expected result is usable?

  2. 2. Case 12 passes only when case 11 ran first and left items in the cart. What is the defect in the case?

  3. 3. Which of these belong in a test case?(choose all that apply)

Answer every question first.