Debugging and Tests
What this concept is
Debugging finds why observed behavior differs from an expectation. A useful test pairs a deliberate input with an independently predicted result. A regression test preserves the case that exposed a bug.
Why it matters here
Passing the happy-path example does not establish that empty input, duplicates and corrupt storage are handled correctly.
Concrete example
Consider this defect:
def total_minutes(sessions):
total = 0
for minutes in sessions:
total = minutes
return total
assert total_minutes([25, 15]) == 40
The assertion fails: actual is 15. The smallest informative case has two different sessions. Trace total: 0, 25, 15. The assignment replaces accumulated work. Repair it to total += minutes, then retain tests for two sessions, one session and the empty list.
Identify which kind of failure you have
A syntax error means Python could not interpret the program's structure, such as a missing colon after if. An exception during execution occurs when an operation fails, such as converting "two" to an integer. A logic error completes execution but gives the wrong result, such as returning only the last session. A successful run therefore does not establish correctness.
Read a traceback from its final exception message upward to the relevant line in your code. If one of your functions called another, both call locations may appear. The last location identifies where the failure surfaced; earlier frames explain how execution got there. Exact formatting varies across Python versions, so learn to find the filename, line and exception type rather than memorizing a screenshot.
Make a hypothesis that predicts a result
For the broken total above, “Python is wrong” provides no useful experiment. “The loop overwrites the total with the current value” predicts that [7, 2] returns 2 and [2, 7] returns 7. “The function always returns the smallest value” predicts 2 for both. Those two inputs distinguish the hypotheses.
Add a temporary print("before", total, "item", minutes) before the update. Observe each iteration, compare it with your hand trace, and repair only the statement contradicted by the evidence. Then remove the diagnostic print and rerun every relevant case. The later design clinic develops this method further.
Give every test a reason
After repairing the function, place these checks below its definition:
assert total_minutes([]) == 0 # no loop iterations
assert total_minutes([7]) == 7 # one iteration
assert total_minutes([7, 2]) == 9 # accumulation across iterations
assert total_minutes([7, 7]) == 14 # repeated values remain distinct
The expected numbers come from independent hand calculation. Testing only one item would not catch the overwrite defect. Testing duplicates catches accidental deduplication. The empty case checks initialization and return placement. Testing invalid input belongs to the function whose contract promises to reject it; the simple total assumes validated values.
Assertions usually say nothing when they pass. Put print("Checks passed") after them if you want a completion message. Run normally, without Python's optimization option: assertions can be disabled by that option, so they are not a replacement for input validation. A finite set of passing cases is evidence, not a proof about all possible inputs.
Preserve a useful debugging note
Write the command and input, expected result, actual result or exception, competing explanations, the observation that selected one explanation, and your repair. End with the test that would fail if the bug returned. If you are stuck, share that small reproducible case rather than your entire project. You can ask for a hint while still doing the prediction and repair yourself.
Common confusion / misconception
A traceback is evidence, not a request to rewrite everything. Read the final exception message, then the relevant source line. A test that repeats the implementation's calculation can repeat its mistake. Catching all exceptions can hide the defect.
How to use it
Use this loop: reproduce, predict, isolate, observe, repair, recheck. Print a temporary intermediate value if needed, or use the editor debugger. Remove temporary output afterward. Run python3 check.py starter from the extracted lab directory.
Check yourself
Why does a one-session test miss this defect? Answer: replacing zero with the only value gives the same result as adding it. Two sessions distinguish the behaviors.
Mini drill or application
Record one real failure from your lab: input, expected result, observed result, hypothesis, observation, fix and regression test. Add a case absent from the supplied checker and explain its purpose.
Read this only if stuck
Official source: Python tutorial. Read only the matching section after attempting the example. These lessons and exercises are original curriculum material.