Debugging with Unit Tests and Assertions
Students diagnose defects in a short program, write targeted unit tests and assertions, and use test evidence to evaluate competing fixes for correctness and reliability.

Illustrations are auto-generated and may be placeholders. They can be refreshed to match the narration.
From Failure Report to Test Case
A failure report is evidence, not yet a test. Translate it into a repeatable example with a stated expected result. Suppose a store promises, “Orders of $50 or more receive free shipping,” but a customer reports a $5.99 charge on a $50.00 order. Record the input, the required rule, the observed output, and the program version. Then form a test: call shipping_cost(50.00) and expect 0.00. Keep unrelated details, such as the customer’s name, out of the test. A strong test fails for the reported reason and can be run automatically. Before changing code, reproduce the failure. If the test unexpectedly passes, investigate differences in tax handling, coupons, data types, or software versions rather than guessing at a fix.

Arrange, Act, and Assert
The Arrange, Act, and Assert pattern makes each test easy to follow. Arrange the input and any required objects: set subtotal to 50.00. Act by calling the function once: set result to shipping_cost(subtotal). Assert that the result equals 0.00. In Python, the key check could be assert result == 0.00. A testing framework can also display a helpful message showing both values when the assertion fails. Each test should focus on one behavior so that a failure has a clear meaning. Assertions may also check internal assumptions, such as a subtotal never being negative. However, programs should use explicit validation for user input because runtime assertions can be disabled. Clear test names, such as test_free_shipping_at_threshold, communicate the rule being verified.

Testing Normal and Edge Cases
A reliable test suite includes normal cases, boundary cases, and invalid inputs. For the shipping rule, $80.00 is a normal free-shipping case, while $20.00 is a normal paid-shipping case. The most important boundary values are $49.99, $50.00, and $50.01 because they surround the threshold. Expected results are $5.99, $0.00, and $0.00, respectively. Also test a negative subtotal and define the intended response, such as raising a ValueError. These cases reveal different defects: a strict greater-than comparison fails at exactly $50.00, while missing validation may accept impossible values. Avoid selecting many random examples that exercise the same path. A small, targeted collection of cases provides stronger evidence when each case represents a distinct requirement, branch, boundary, or failure mode.

Tracing and Isolating the Defect
When the boundary test fails, trace the program from input to output instead of changing code immediately. Suppose shipping_cost contains the condition subtotal > 50. The trace for 50.00 asks whether 50.00 is greater than 50, produces false, follows the paid-shipping branch, and returns 5.99. This trace matches the observed failure. The requirement, however, says “$50 or more,” which means the comparison must include equality. Tests at $49.99 and $50.01 already pass, so the evidence isolates the defect to the boundary operator rather than the returned prices or the function call. A trace table can record the input, condition result, selected branch, and output. Isolating the first point where actual behavior differs from required behavior reduces the risk of unnecessary changes elsewhere.

Comparing Competing Fixes
Competing fixes should be evaluated with the same tests and prioritized criteria. Fix A changes subtotal > 50 to subtotal >= 50. Fix B adds one cent before using the original comparison. Both may make the reported $50.00 test pass, but that single result is insufficient. Run the full suite, especially $49.99, $50.00, and invalid inputs. Fix A directly represents “$50 or more,” is easy to review, and does not alter the subtotal. Fix B hides the boundary rule inside arithmetic and may behave unpredictably with fractional cents or floating-point rounding. Compare correctness, clarity, reliability, compatibility, and customer impact. Fix A is better supported because its logic matches the requirement and creates fewer new risks. The argument should acknowledge that even Fix A does not solve separate issues involving currency representation or missing input validation.

Documenting Evidence and Remaining Risks
A debugging record should connect the requirement, evidence, fix, and conclusion. State that orders of $50 or more require free shipping. Record that the original program failed the $50.00 boundary test by returning $5.99, while the revised comparison returned $0.00 and preserved expected results for $49.99, $50.01, $20.00, and $80.00. Explain why the inclusive comparison was selected over the arithmetic adjustment. Also report limitations: passing tests do not prove correctness for every possible input. Remaining risks may include floating-point currency errors, unclear treatment of coupons and taxes, negative values, and differences between software versions. Recommend using a decimal currency type, adding validation tests, and confirming whether the threshold applies before or after discounts. This evidence-based explanation acknowledges both the fix’s strengths and its unresolved weaknesses.

