Test cases:
the recipe card for finding bugs.
A test case isn't a magic wand that makes software better — weighing yourself doesn't reduce your weight, and going to the doctor doesn't make you healthy. What it does is give you a precise, repeatable recipe: exact inputs, exact steps, exact expected outcome — so that anyone, any time, can run the same check and get the same answer. This chapter is about writing that recipe card well.
Testing doesn't fix your software
Get this uncomfortable truth locked in before anything else — it reframes everything that follows.
The massive misunderstanding
from the slides, verbatim ideaThere is a massive misunderstanding about testing: that it improves software. It doesn't. What testing (and the scale, and the doctor's visit) actually does is help identify problems that you might then choose to resolve. Testing does not make the product better — even though it's part of a larger process that does make a product better.
Five parts, one repeatable recipe
Three overlapping definitions from the deck, all pointing at the same thing.
A unique identifier
So a specific test case can be referenced, tracked, and re-run — e.g. TC-47.
The requirement this test case is exercising
Ties the test back to a documented requirement — e.g. FR-4, "case sensitivity
in search-and-replace."
The state of the software before the test
What must already be true/loaded/set up before step 1 can run — e.g. "test document TESTDOC.DOC is loaded (base state BS-12)."
The specific steps that make up the interaction
A numbered, exact sequence of actions — click this, type that — with concrete data, not vague descriptions.
The expected state of the software after execution
Every test case must have an expected result. No expected result means there's nothing to compare against — and nothing to call a pass or a fail.
This is the Search Term."Before you write a single step
1. Understand the language fundamentals
Sizes and limits of variables, platform-specific information.
2. Understand the domain
What real-world problem is this software solving?
3. Read the requirements
The spec is the ground truth for what "correct" means.
Think like a user
What possible things do they want to do?
Think about possible "mistakes"
i.e. invalid input — what will a careless or confused user type?
Think about impossible conditions
Inputs or states that should never legitimately occur — test them anyway.
What is the testing intended to prove?
3 goalsSeven habits of a strong test case
Simple & transparent
Anyone should be able to read it and immediately know what it's checking.
End user in mind
Write it the way a real user would interact with the system, not just how a developer would.
Avoid repetition
Don't write ten test cases that all test the same thing in slightly different words.
Do not assume
Stick to the specification documents — don't fill gaps with guesses about intended behavior.
Ensure 100% coverage
Every requirement should be traceable to at least one test case.
Must be identifiable
Unique names/numbers so a failing test can be reported and tracked precisely.
Implement testing techniques
you can't test everythingIt's not possible to check every possible condition in your software application. Testing techniques help you select a few test cases with the maximum possibility of finding a defect.
Inputs and outputs, or actions and expected results — a test case must have an expected result.
✓ Testing unexpected conditions
✓ Bad (illegal) input values
✓ Boundary conditions
Three angles of coverage, in general
applies to any inputCover all valid input
Try multiple sets of values, not just one. Try permutations of values.
Check boundary conditions
Check for off-by-one conditions specifically.
Check invalid input
Illegal sets of value, illegal input / impossible conditions, and totally bad input (text vs. numbers, etc.).
The triangle-classification problem
The deck's running example for applying the three coverage angles above to a real function: classify a triangle given three side lengths.
Valid input
cover all 3 shapesAll three possible conditions: equilateral, isosceles, scalene. Try multiple sets of values, and permutations of the same set.
{3,4,5},
{4,3,5}, {5,4,3} — same triangle, different argument order.Boundary conditions
off-by-oneCheck the off-by-one edges of whatever range the values can take.
0 and MAX_INT —
the smallest and largest representable side lengths.Invalid input
three distinct flavorsWrong format:
not integer.
Negative numbers.{2,3,8}, {2,3,5} — these fail the triangle inequality
(the definition of a triangle), so no actual triangle can have these sides.How to actually run it
stdin / stdoutHave the code read from standard input and print to standard output, so the whole test suite can run unattended and get diffed against expected output.
java Triangle < testcases.txt # or interactively: java Triangle 2 3 3 3 4 5 # print output to a file: java Triangle > results.txt # combine both: java Triangle <testcases.txt >results.txt
Beware of problems with comparisons
Two comparison bugs that are easy to write and easy to test for once you know to look.
Comparing floats
never do thisfloat a, b; ... if (a == b) // NEVER
Is it 4.0000000, or
3.9999999, or 4.0000001? What is your limit of accuracy? Exact
float equality is almost never the right check.
Objects vs. references
OO languagesString a = "Hello world!\n"; String b = "Hello world!\n"; if ( a == b ) // reference check vs. if ( a.equals(b) ) // content check
In object-oriented languages, make sure you know whether you're comparing the contents of an object or the reference to an object — they can silently give different answers.
The same feature, tested two ways
Both examples are testing search-and-replace case sensitivity. Only one of them could actually be repeated by a stranger.
TC-47 — the good example
✓ data-specific| Name | TC-47: Verify that lowercase data entry results in lowercase insert |
|---|---|
| Requirement | FR-4 (Case sensitivity in search-and-replace), bullet 2 |
| Preconditions | The test document TESTDOC.DOC is loaded (base state BS-12). |
| Steps | 1. Click "Search and Replace." 2. Click in "Search Term" field. 3. Enter This is the Search Term. 4. Click in "Replacement Text" field. 5. Enter This IS THE Replacement TeRM. 6. Verify "Case Sensitivity" checkbox is unchecked. 7. Click OK. |
| Expected results | 1. Search-and-replace window is dismissed. 2. Verify that in line 38, this is the search term has been replaced by this is the replacement term. 3. Return to base state BS-12. |
The bad example
✗ vague, not repeatable| Steps | 1. Bring up search-and-replace. 2. Enter a lowercase word from the document in the search term field. 3. Enter a mixed-case word in the replacement field. 4. Verify case sensitivity is not turned on and execute the search. |
|---|---|
| Expected results | 1. Verify that the lowercase word has been replaced with the mixed-case term in lowercase. |
Hygiene rules for the test code itself
One thing at a time per test method
10 small tests are much better than 1 test that's 10x as large.
Few (likely 1) assert statements
If you assert many things, the first failure stops the test — you won't know whether a later assertion would also have failed.
Avoid logic
Minimize if/else, loops, switch. Avoid try/catch — if it's supposed to throw, use
expected=; if not, let the test framework catch it.
Running the plan, then running it again
When does it start?
the alpha buildSoftware testers begin executing the test plan after the developers deliver the alpha build — a build they feel is feature-complete. The alpha should already be high quality: developers should feel it's ready for release and as good as they can get it.
Focus on what's new
First pass targets the newly added or changed features.
Make sure nothing else broke
Verify a change to one area of the software hasn't caused problems anywhere else. Regression testing usually means re-executing all test cases previously executed. There are typically at least two regression tests for any software project.
When is testing complete?
table 8-6Either no defects are found, or defects meet the acceptance criteria outlined in the test plan. A representative acceptance-criteria checklist:
| 1 | Successful completion of all tasks documented in the test schedule. |
| 2 | Quantity of medium- and low-level defects at an acceptable level, per the QA team lead. |
| 3 | User interfaces for all features are functionally complete. |
| 4 | Installation documentation and scripts are complete and tested. |
| 5 | Development code reviews complete; all high-priority issues resolved. |
| 6 | All outstanding issues pertinent to this release are resolved and closed. |
| 7 | All code under source control, build process automated, components labeled with correct version numbers. |
| 8 | All high-priority defects corrected and fully tested prior to release. |
| 9 | Unfixed defects reviewed by stakeholders and confirmed acceptable. |
| 10 | The end-user experience is at an agreed acceptable level. |
| 11 | Operational procedures written for install, setup, error recovery, escalation. |
| 12 | No adverse effects on already-deployed systems. |
The code you build to support testing
From test case specifications to concrete test cases: test design often yields specifications ("a large positive number," "a sorted sequence, length > 2") rather than literal data. Generation turns those into concrete, executable test cases.
Scaffolding, defined
like construction scaffoldingCode produced to support development activities (especially testing) — not part of the "product" as seen by the end user, and may be temporary, just like scaffolding around a building under construction. Includes test harnesses, drivers, and stubs.
Test driver
A "main" program for running a test. May be produced before the "real" main program, and gives more control — it exists to drive the program under test through its test cases.
Test stub
A substitute for called functions/methods/objects that the code under test depends on, but that aren't being tested right now.
Test harness
Substitutes for other parts of the deployed environment — e.g. a software simulation of a hardware device.
Real modules (solid) surrounded by stubs (dashed) that stand in for anything not currently under test.
How do you know it passed or failed?
No use running 10,000 test cases automatically if the results still have to be checked by hand.
Comparison-based oracle
predicted outputNeeds a predicted output for every input. The oracle compares actual
output to predicted output and reports failure if they differ. Fine for a small number
of hand-generated test cases — e.g. hand-written JUnit tests, where assert
is the specific oracle coded by hand in each test case.
Self-checking code
judge without predictingThe oracle is written as self-checks inside the program itself — often possible to judge correctness without predicting the exact result. Usable with large, automatically generated test suites, but often only a partial check (e.g. structural invariants of data structures) — recognizes many or most failures, but not all.
Capture and replay
for GUI testingSometimes there's no alternative to human input and observation — even after separating program functionality from the GUI, some GUI testing is still required. You can at least cut the repetition of human testing: capture a manually run test case, then replay it automatically.
Defect tracking
workflowA defect tracking system records and tracks defects. It routes each defect between testers, developers, the project manager, and others, following a workflow designed to ensure the defect is verified and repaired.
Smoke tests
shallow but fastA subset of test cases typically representative of the overall test plan. Good for verifying proper deployment or other non-invasive changes, and for confirming a build is ready to send to test. Not a substitute for actual functional testing.
The whole chapter in two tables
| Concept | What it means | Anchor example |
|---|---|---|
| Test case | Set of conditions/steps a tester runs to check one behavior against expected results | TC-47 |
| 5 parts | Name · Requirement · Preconditions · Steps · Expected Results | TC-47 table |
| BVA | Testing the boundaries of a value range | 0, MAX_INT triangle sides |
| Scaffolding | Support code: drivers, stubs, harness — not shipped to users | JUnit, Eclipse |
| Test driver | "Main" program that drives code under test through test cases | Custom main / JUnit runner |
| Test stub | Stand-in for a called function/method/object not under test | stub B, stub X |
| Oracle | Decides pass/fail — comparison-based (predicted output) or self-checking (invariants) | assert vs. structural checks |
| Capture & replay | Record a manual GUI test once, replay automatically | Comparison-based replay |
| Regression testing | Re-run all previously-passed tests after a change | ≥ 2 rounds per project |
| Smoke test | Representative subset confirming a build is deployable | Not a full functional pass |
| Invalid-input flavor | Means | Triangle example |
|---|---|---|
| Illegal sets of value | Wrong format or negative numbers | "not integer",
-3 |
| Illegal input | Impossible conditions | {2,3,8},
{2,3,5} |
| Totally bad input | Wrong type entirely | Text vs. numbers |
Where students actually lose points
Every one of these is a real mix-up the slide content sets up directly.
1. "Testing improves the software"
Running more tests makes the product better on its own.
Testing only identifies problems. It's part of a process (design → fix → retest) that improves the product — the testing step itself doesn't. Like the scale not reducing your weight.
2. A test case without an expected result
Writing steps and calling it done — "click OK, see what happens."
A test case must have an expected result. Without one there's nothing to compare the actual outcome to, and no way to call it pass/fail.
3. Vague, non-data-specific steps
"Enter a lowercase word" or "a mixed-case word" (the bad TC example).
Good test cases are data-specific: "Enter This is the Search Term." Two different testers running vague steps can get different results — that breaks repeatability.
4. float a == b
Comparing two floating-point
numbers with == and expecting a reliable true/false.
Floats accumulate rounding error
(4.0000000 vs 3.9999999) — compare within a tolerance / limit of
accuracy instead.
5. a == b on objects when you meant content equality
Using == on two
String/object variables to check if their contents match.
== checks the
reference; a.equals(b) checks the content. Know which one your
test actually needs.
6. Confusing "illegal input" with "illegal set of values"
Treating {2,3,8}
(impossible triangle) the same as -3 or "not integer" (illegal format).
They're different flavors: illegal set of values = wrong format/negative; illegal input = values that are individually fine but together violate a domain rule (impossible condition, like the triangle inequality); totally bad input = wrong type entirely (text vs. numbers).
7. Stuffing many asserts into one test method
One giant test method with five asserts chained together.
If the first assert fails, the rest never run — you won't know if a later assertion would also have failed. Prefer few (likely 1) asserts per test, and 10 small tests over 1 test that's 10x as large.
8. Comparison-based oracle assumed to scale to any suite size
Assuming you always need a predicted output for every input, even for 10,000 auto-generated test cases.
Comparison-based oracles fit small, hand-written suites. Large, auto-generated suites usually need self-checking code instead — which is often only a partial check, but scales.
Quick self-check
Click any question to reveal the answer.
1. Does testing improve software?
Reveal2. Name the five parts of a typical test case table.
Reveal3. Short answer: for the triangle problem, give one example each of a boundary value and an "illegal input / impossible condition."
Reveal0 or MAX_INT as a side
length. Illegal input / impossible condition: {2,3,8} or
{2,3,5} — these violate the triangle inequality, so no triangle can have those
sides.4. Multiple choice: which of these is the safest way to compare two floats for
equality? (a) a == b (b) compare within a tolerance/accuracy limit (c)
a.equals(b)
== on floats — rounding error means
4.0000000, 3.9999999, and 4.0000001 may all "mean"
4.0. .equals() is for object content comparison, not float precision.5. What's the difference between a test stub and a test driver?
Reveal6. True or False: a comparison-based oracle is the only way to automatically judge pass/fail.
Reveal7. Why is a smoke test not a substitute for functional testing?
Reveal8. Why should a test method have "few (likely 1)" assert statements?
RevealApply the coverage angles
Realistic scenario questions grounded in the deck's own triangle and search-and-replace examples.
shoug_a") and a precise,
verifiable expected result (e.g. "account is created and the dashboard loads").