Understand the idea
A problem you can recognise
A program prints the answer you expected once. Should you trust it with every student's plan? A single successful run answers only a narrow question. Testing makes those questions explicit and repeatable, while debugging investigates why a particular expectation failed.
The goal is not to make a green message appear at any cost. It is to keep useful evidence that the program follows its stated rules.
Understand the idea
Understand the idea
A test compares observed behaviour with an explicit expectation. It is stronger than glancing at output and deciding it looks plausible. Start with a small function whose return contract is clear, then cover a normal case, a boundary and a failure case. A passing example proves that particular check, not every possible property of the program.
Assertions are convenient for small learning checks, but ordinary assert statements can be disabled when Python runs with optimisation. Do not use assert as the only protection for user input, authentication or another required production invariant. Explicit validation should raise or return an intentional failure independently of optimisation settings. unittest supplies test-case methods for a structured suite.
Debugging begins with reproducing the defect. Reduce the input, locate the first point where observed state differs from the prediction, and fix the cause rather than editing the expected output until the test turns green. Keep a regression check that would have failed before the fix. A test that never calls the relevant function, or only repeats its implementation in a different spelling, can create false confidence.
Check your understanding
Example 1: Check an exact return value
PYTHON exampleRead the code, then try a change.
Try this example
def total(days, minutes):
return days * minutes
assert total(5, 30) == 150
assert total(0, 30) == 0
print("Two checks passed")
Expected output:
ReferenceCompare this with the explanation.
Two checks passed
The second assertion tests the zero-day boundary. The message is printed only after both assertions succeed in this normal interpreter configuration.
Check your understanding
Example 2: Check a required failure
PYTHON exampleRead the code, then try a change.
Try this example
def checked_minutes(value):
if value < 0:
raise ValueError("Negative duration")
return value
try:
checked_minutes(-1)
except ValueError:
print("Expected rejection")
else:
raise RuntimeError("Missing rejection")
Expected output:
ReferenceCompare this with the explanation.
Expected rejection
The else branch would fail the check if the invalid input were accepted. Merely catching an exception without an explicit no-exception failure would not prove the rejection happened.
See how it works
Example 3: Use a structured test case
PYTHON exampleRead the code, then try a change.
Try this example
import unittest
class TotalChecks(unittest.TestCase):
def test_product(self):
self.assertEqual(5 * 30, 150)
result = unittest.TestResult()
TotalChecks("test_product").run(result)
print(result.testsRun)
print(result.wasSuccessful())
Expected output:
ReferenceCompare this with the explanation.
1
True
The test case executes one named test and records its result without printing an environment-dependent runner banner. A real application would test its actual function rather than only this introductory arithmetic expression.
Understand the idea
Think through the important decisions
Choose an expectation from the problem, not from whatever the current implementation happens to return. For a study total, calculate one small result independently. Then add a zero-day case and a required rejection. These cases exercise different parts of the contract rather than repeating the same happy path with larger numbers.
An expected-failure check must also fail if no exception is raised. Merely placing invalid input inside try and printing a message in except does not prove anything when the except branch never runs. The example's else branch makes the absence of the required rejection observable.
When a check fails, shrink the input until the first wrong step is easy to inspect. Compare predicted and actual state at that step. Repair the cause, rerun the failed check and keep it as a regression case. Changing a correct expectation to match a bug removes the evidence you needed.
Check your understanding
Check the reasoning
Test normal cases, boundaries and expected failures; a passing test does not prove all possible behaviour.
A matching screenshot or output is one check, not proof that a program is correct for all inputs. Change one input, predict the new result and run again. If the result differs, find the first instruction where your prediction stops matching the program state. Keep that small case as a regression check when you fix the cause.
Spot the mistake
The mistake worth understanding
Python can disable assert statements under optimisation. Use them as demonstrated for learning checks, not as the only protection for untrusted production input. Also ensure your tests call the real function whose behaviour matters instead of repeating its formula in another place.
Your turn
Independent practice
Write a study-total function and checks for ordinary input, zero days and a negative-duration rejection. Deliberately introduce an off-by-one error, observe which check catches it, then fix the implementation without changing the valid expectation.
Your turn
Practice hint
Choose expectations from the problem statement. Ensure the expected-failure check also fails when no exception is raised.
Change it and explain it
Experiment with the first program Write a study-total function and checks for ordinary input, zero days and a negative-duration rejection. Deliberately introduce an off-by-one error, observe which check catches it, then fix the implementation without changing the valid expectation. Start by changing one input in this smaller example. Loading it does not execute it.
def total(days, minutes):
return days * minutes
assert total(5, 30) == 150
assert total(0, 30) == 0
print("Two checks passed") Open in Practice Loading an example does not run it. Your existing edits are protected by a replacement confirmation.
Your turn
Try a different case
Introduce one deliberate off-by-one error and identify the check that catches it. Fix the implementation without weakening that check. Explain one behaviour your tests cover and one behaviour you have not yet tested.