Designing Unit Tests with Boundary Cases
Students analyze a short function, identify normal and boundary inputs, and write unit tests that provide evidence of the program’s correctness and reveal defects.

Illustrations are auto-generated and may be placeholders. They can be refreshed to match the narration.
Purpose of Unit Testing
A unit test checks one small part of a program in isolation and compares its actual result with an expected result. Small tests help programmers build an evidence-based argument that a function follows its requirements. Consider an admissionPrice(age) function. It should return 8 dollars for ages 0 through 12, 12 dollars for ages 13 through 64, and 9 dollars for ages 65 through 120. A test using age 30 checks one ordinary adult case, but it does not prove that every age is handled correctly. Several focused tests provide stronger evidence and make defects easier to locate. Unit testing cannot prove that a program is perfect, but carefully selected cases can show that important rules work and can expose mistakes before the function becomes part of a larger system.

Reading the Function Contract
Before choosing tests, read the function contract and separate it into manageable rules. A contract describes valid inputs, expected outputs, and required behavior for invalid inputs. For admissionPrice(age), suppose age must be an integer from 0 through 120. The function returns 8 dollars for ages 0 through 12, 12 dollars for ages 13 through 64, and 9 dollars for ages 65 through 120. Any age below 0 or above 120 must cause a RangeError. A non-integer, such as 12.5, must cause a TypeError. These statements create five smaller testing problems: three valid price groups and two categories of invalid input. Do not invent behavior that the contract does not require. If the contract says nothing about numeric strings such as "13," ask for clarification rather than assuming they are accepted.

Choosing Normal and Boundary Inputs
Normal inputs lie comfortably inside a category, while boundary inputs occur at or immediately beside a point where behavior changes. For admissionPrice, ages 6, 30, and 80 are normal inputs for the child, adult, and senior groups. The important valid boundaries are 0, 12, 13, 64, 65, and 120. Invalid values just outside the allowed range are -1 and 121. Testing both sides of each transition is important because comparison errors often appear there. For example, code using age < 12 instead of age <= 12 would incorrectly charge a 12-year-old the adult price. Testing only age 6 would miss this defect. A strong test set therefore includes representative normal cases, every transition pair, and selected invalid cases. Extra random inputs may help, but they should not replace carefully reasoned boundary cases.

Writing Expected Results and Assertions
Each test should state an input, determine the expected result from the contract, call the function, and compare the actual result with that expectation. For example, a test for age 12 expects admissionPrice(12) to equal 8. A test for age 13 expects admissionPrice(13) to equal 12. Together, these assertions check both sides of a price transition. Error behavior also needs explicit assertions. A test for age -1 should assert that the call throws a RangeError, while a test for 12.5 should assert that it throws a TypeError. Expected results must be written before relying on the program’s output; otherwise, a programmer may accidentally treat defective behavior as correct. Give each test a precise name, such as returnsChildPriceAtUpperBoundary, so a failure communicates which contract rule needs attention.

Running Tests and Diagnosing Failures
When tests run, each assertion produces a pass or failure. A failure is evidence of a mismatch, not an automatic explanation of its cause. Suppose the test for age 12 expects 8 dollars but receives 12 dollars, while tests for ages 11 and 13 pass. Compare the failing evidence with the source code and contract. A condition written as age < 12 is a likely cause because it excludes 12 from the child group. Change it to age <= 12, then run the entire test suite again. Re-running all tests checks that the repair did not damage another rule. Also consider whether the test itself is wrong; an expected value that contradicts the contract must be corrected. Diagnosis is strongest when it cites the failing input, expected result, actual result, relevant requirement, and a reasoned explanation of the defect.

Evaluating Test Coverage
Test coverage describes how thoroughly a test suite examines the required behavior. For admissionPrice, create a checklist for the three valid price groups, both sides of each transition, minimum and maximum valid ages, out-of-range values, and wrong input types. A suite containing ages 6, 30, and 80 covers every price group but has weak boundary coverage. Adding 0, 12, 13, 64, 65, 120, -1, 121, and 12.5 provides stronger evidence. However, even this suite has limits. It may not test values such as null, extremely large numbers, or failures caused by other parts of an application unless the contract requires them. Coverage percentages alone do not prove correctness because code can execute without its result being meaningfully checked. Evaluate coverage by explaining which requirements have evidence, which defects the tests could reveal, and which risks remain untested.

