Dokven

Loading Dokven.
Core skills 8 min read

How to write a great test case

The anatomy of a test case (ID, preconditions, steps, test data, expected vs actual result), with real worked examples and what separates a good test case from a useless one.

A test case is a recipe. Hand it to someone who has never seen your app, and they should be able to follow it exactly and reach the same result you would. No guessing, no "you kind of click around here". A recipe that only works when the original chef is in the kitchen is a bad recipe, and a test case that only you can run is a bad test case.

In Foundations you learned that testing is asking software good questions. A test case is one of those questions, written down precisely enough that anyone can ask it and recognise a wrong answer. Let's take it apart piece by piece.

The anatomy of a test case

Most test cases share the same skeleton. You don't always fill in every field, but knowing the full list keeps you honest:

FieldWhat it holdsExample
Test case IDA short unique label so you can refer to it later.TC-LOGIN-007
TitleA one-line summary of what this checks.Login fails with a wrong password
PreconditionsWhat must already be true before you start.A registered account exists; you are on the login page.
Test stepsThe exact actions, in order.1. Type a valid email. 2. Type a wrong password. 3. Click Log in.
Test dataThe specific values you'll use.Email: amy@test.com · Password: wrongpass123
Expected resultWhat should happen if the software is correct.An error "Email or password is incorrect" appears; you stay logged out.
Actual resultWhat did happen when you ran it.(filled in at run time)
StatusPass or Fail, decided by comparing the two above.Pass / Fail
The heart of every test case

The two fields that matter most are expected result and actual result. A test passes or fails purely by comparing them. If you can't state the expected result before you run the test, you don't have a test: you have a wander.

Worked example: a login screen

Let's write two real cases for a login form. The first is a positive case (we feed it good data and expect success). The second is a negative case (we feed it bad data and expect a graceful failure). You need both: software that only handles the happy path is software that will embarrass you.

IDTitleStepsTest dataExpected result
TC-LOGIN-001Login succeeds with valid credentials1. Open login page. 2. Enter email. 3. Enter password. 4. Click Log in.amy@test.com / Correct123!User lands on the dashboard; their name shows in the header.
TC-LOGIN-007Login fails with a wrong password1. Open login page. 2. Enter valid email. 3. Enter a wrong password. 4. Click Log in.amy@test.com / wrongpassAn inline error "Email or password is incorrect" appears; user stays on the login page.

Notice TC-LOGIN-007 expects a vague-on-purpose message ("Email or password is incorrect") rather than "wrong password". That's a real security habit: telling an attacker which half they got right is a gift. A sharp tester checks not just that login fails, but that it fails the right way.

Worked example: an e-commerce coupon

Coupons are a goldmine of bugs because money is involved and the rules are fiddly. Imagine a code SAVE20 that gives 20% off orders over $50, expires at the end of the month, and can be used once per customer. Here's a small suite:

IDTitleTest dataExpected result
TC-COUPON-001Valid coupon applies on a qualifying orderCart total $80 · code SAVE20Total drops to $64; a "20% off applied" note shows.
TC-COUPON-002Coupon rejected below the minimum spendCart total $40 · code SAVE20Code is refused with "Spend $50 to use this code"; total unchanged.
TC-COUPON-003Expired coupon is refusedCart total $80 · code SAVE20 · system date next monthCode refused with "This code has expired"; total unchanged.
TC-COUPON-004Same coupon cannot be reused by one customerSame account, second order, code SAVE20 againCode refused with "You've already used this code"; total unchanged.

Four small cases, and already you're covering the discount maths, the minimum-spend rule, the expiry, and the one-use limit. This is what people mean by coverage: not testing everything, but testing each rule that could plausibly break.

What separates a good test case from a bad one

  • Clear. Anyone on the team can read it and run it the same way. No private context left in your head.
  • Independent. It doesn't secretly depend on another test running first. If TC-3 only passes when TC-2 ran moments before, you'll get phantom failures forever.
  • Reproducible. Same steps and data give the same result every time. If it passes on Tuesdays and fails on Fridays, the test data or setup is leaking.
  • Specific. "Check the cart works" is a wish, not a test. "Adding a second unit updates the total to $128" is a test.
  • Atomic. One case checks one thing. Bundle five checks together and a failure tells you almost nothing about which part broke.
The most common beginner trap

Writing the expected result after running the app, copying down whatever it did and calling it "expected". That's not testing, that's note-taking. If the app already had a bug, you just enshrined the bug as correct. Always decide what should happen before you look.

Key takeaways
  • A test case is a recipe precise enough that anyone can follow it and reach the same result.
  • The full skeleton: ID, title, preconditions, steps, test data, expected result, actual result, status.
  • Expected vs actual is the heart of it: write the expected result before you run the test.
  • Write both positive cases (good data, expect success) and negative cases (bad data, expect a graceful failure).
  • Good test cases are clear, independent, reproducible, specific, and atomic: one check each.