6.1.6 Testing and evaluating programs
Testing a Program
Testing verifies that a program produces correct outputs for a range of inputs. A program is fit for purpose if it meets its specification correctly for all expected and unexpected inputs. Structured testing uses three categories of test data to expose different types of failure.
| Test type | Description |
|---|---|
| Normal | Typical valid inputs that the program should handle in routine operation |
| Boundary | Values at the exact limits of valid ranges — tests whether edge conditions are handled correctly |
| Erroneous | Invalid inputs the program should reject or handle gracefully without crashing |
Worked Example: Testing a Score Validator
Program: accepts an exam score (0–100), prints "Valid" or "Invalid".
def validate_score(score):
"""Returns True if score is between 0 and 100 inclusive."""
return score >= 0 and score <= 100
score_input = int(input("Enter score: "))
if validate_score(score_input):
print("Valid")
else:
print("Invalid")
| Test type | Input | Expected output | Actual output | Pass/Fail |
|---|---|---|---|---|
| Normal | 55 | Valid | Valid | Pass |
| Normal | 72 | Valid | Valid | Pass |
| Boundary | 0 | Valid | Valid | Pass |
| Boundary | 100 | Valid | Valid | Pass |
| Boundary | -1 | Invalid | Invalid | Pass |
| Boundary | 101 | Invalid | Invalid | Pass |
| Erroneous | -50 | Invalid | Invalid | Pass |
| Erroneous | 200 | Invalid | Invalid | Pass |
Boundary values (0, 100, -1, 101) test whether the exact limits are handled correctly — a common off-by-one error would use > instead of >=, causing 0 to be rejected when it should be valid.
Evaluating Efficiency
A program is efficient if it solves its problem using as few operations as possible. For GCSE, efficiency is evaluated in terms of:
- Number of comparisons: how many times does the program evaluate a condition?
- Number of passes through a loop: how many iterations does the loop perform?
- Memory use: does the program store more data than necessary?
# Less efficient: continues checking even after target is found
def contains(items, target):
found = False
for item in items:
if item == target:
found = True # Sets flag but keeps looping
return found
# More efficient: exits loop immediately on finding the target
def contains_efficient(items, target):
for item in items:
if item == target:
return True # Returns immediately — no unnecessary iterations
return False
For a 1000-element list where the target is at index 0, the first version makes 1000 comparisons; the second makes 1.
Key Takeaways
- Test with normal (typical), boundary (limits) and erroneous (invalid) data.
- Boundary testing is critical — off-by-one errors (
>vs>=) only show up at the exact limits. - A test table records: input, expected output, actual output, pass/fail.
- Efficiency: fewer comparisons and loop iterations = more efficient; early exit from loops improves best-case performance.