SDD  ·  Software Design & Development

Testing: Errors and Test Plans

Lesson 15 of 18 Approx 60 min Requires: SDD5 Variables & Data Types
Learning intentions
  • Understand the three types of programming error — syntax, execution, and logic — and how they differ
  • Select appropriate normal, extreme, and exceptional test data for a given program
  • Design a complete test plan that documents test cases with expected results
Success criteria
  • I can identify whether a given error is a syntax, execution, or logic error and justify my answer
  • I can explain what each error type does — whether the program fails to run, crashes at runtime, or gives wrong output
  • I can give an example of each error type in Python code
  • I can select appropriate normal, extreme, and exceptional test data for a program, stating expected results
  • I can design a complete test plan using a table with test number, description, test data, expected result, and type
Warm up — what do you already know?

Answer before the lesson begins. These check prior knowledge — it's fine if you're unsure.

1. A pupil types primt("Hello") in Python. The program does not run at all and Python immediately shows an error. What type of error is this?

2. A program accepts pupil ages from 11 to 18 (inclusive). Which of the following is the best example of extreme test data?

3. A program runs without crashing, but every total it calculates is slightly too high. What type of error is this?

Key vocabulary

syntax error
An error caused by breaking the grammar rules of the programming language. Python detects it immediately — the program will not run at all.
execution error
An error that occurs while the program is running, causing it to crash and stop. Also called a run-time error. Example: dividing by zero or accessing an index that does not exist.
logic error
An error where the program runs to completion without crashing, but produces incorrect output. The hardest type to find — Python gives no error message.
test data
Values used to check that a program works correctly. Good test data covers normal inputs, boundary values, and invalid inputs.
normal test data
Typical values well within the valid range that the program should accept and process correctly. Also called valid data.
extreme test data
Values at the exact boundary of the valid range — the minimum and maximum acceptable values. Also called boundary data. The program should accept these.
exceptional test data
Values outside the valid range, or the wrong data type entirely. The program should reject these. Also called erroneous or invalid data.
test plan
A table documenting test cases: each row states the test data, expected result, and type of test (normal, extreme, or exceptional). Required as evidence in the N5 assignment.

Testing: Errors and Test Plans

Why testing matters

Testing is a fundamental stage in the software development process. Once a program has been written, it must be tested systematically to verify that it works correctly for all expected inputs — and that it handles unexpected inputs gracefully. A program that works for one input but fails for another is not finished. The SQA N5 specification requires you to understand the types of errors that can occur and how to design a structured test plan to catch them.

Type 1 — Syntax errors

A syntax error occurs when the code breaks the grammar rules of Python. Python reads through your code before running it, and if it finds a syntax error it stops immediately and displays an error message. The program never runs at all.

Common causes of syntax errors include:

  • A missing colon after if, elif, else, for, while, or def
  • Wrong or missing indentation
  • An unclosed bracket, parenthesis, or quotation mark
  • A misspelled keyword (e.g. primt instead of print)
  • Using = where == is needed inside a condition

Syntax errors are the easiest to fix — Python points directly to the line where the problem was detected. Once fixed, the program will at least start running.

if age >= 16    # SyntaxError: expected ':' at end of if statement
    print("Old enough")

Type 2 — Execution errors (run-time errors)

An execution error (also called a run-time error) occurs after the program has started running. The syntax is correct, so Python has no complaints before the program begins — but something goes wrong during execution and the program crashes with an error message.

Common causes of execution errors include:

  • Dividing by zero (ZeroDivisionError)
  • Accessing a list index that does not exist (IndexError)
  • Trying to convert an incompatible value, such as int("hello") (ValueError)
  • Using a variable before it has been assigned a value (NameError)

Execution errors are often caused by unexpected user input. For example, a program that expects a number will crash if the user types a word instead.

scores = [10, 20, 30]
print(scores[5])   # IndexError: list index out of range
                   # (valid indices are 0, 1, 2 — there is no index 5)

Type 3 — Logic errors

A logic error occurs when the program runs from start to finish without crashing, but the output is wrong. Python gives no error message because the code is syntactically valid and executes without incident — the problem is in the programmer's reasoning, not in the code structure.

Logic errors are the hardest to find because there is no error message to guide you. You must trace through the code manually, checking each step, to spot where the logic breaks down.

Common causes of logic errors include:

  • Using > when you meant >= (or vice versa) — causes off-by-one issues at boundaries
  • Using the wrong operator: + instead of *, or - instead of /
  • Initialising a variable to the wrong value (e.g. starting a minimum-finder at 0 instead of array[0])
  • A loop running one iteration too many or too few
total = 0
for i in range(1, 6):
    total = total * i   # Logic error: should be total + i
print(total)            # Outputs 0 (not 15) — no error message, just a wrong answer

The trace: total=0. i=1: 0*1=0. i=2: 0*2=0. i=3: 0*3=0. …. Because 0 multiplied by anything is always 0, total never changes. The correct line is total = total + i.

Summary — the three error types at a glance

Error typeWhen detectedProgram runs?Error message?Typical cause
SyntaxBefore runningNever startsYes — points to the lineMissing colon, typo in keyword, unclosed bracket
ExecutionDuring runningStarts, then crashesYes — states the error typeDivide by zero, invalid index, wrong data type conversion
LogicWhen checking outputRuns fullyNo — output is wrong silentlyWrong operator, bad initialisation, off-by-one in loop

Types of test data

When testing a program, you must choose test data that covers all scenarios. The SQA specification requires three types:

Normal test data — typical, realistic values well within the valid input range. The program should accept these and produce the correct output. Example: for a program that accepts scores from 0 to 100, normal data might be 45, 67, or 80.

Extreme (boundary) test data — values at the exact edges of the valid range: the minimum and maximum acceptable values. The program should accept these. Extreme data is critical because off-by-one logic errors (using > instead of >=) only reveal themselves at the boundary. Example: for 0 to 100, extreme data is exactly 0 and exactly 100.

Exceptional test data — values outside the valid range, or the wrong data type altogether. A well-written program should reject these and display an appropriate error message. Example: for 0 to 100, exceptional data might be -1, 101, or the text "hello".

Designing a test plan

A test plan is a structured table listing every test case. Writing the test plan before running the program forces you to think about what the correct behaviour should be — the expected result must be written down before testing, not inferred from the output. This is what makes testing rigorous rather than guesswork.

A complete test plan includes for each test case:

  • Test number — a reference identifier
  • Description — what the test is checking
  • Test data — the specific value(s) used
  • Expected result — what the program should do with this input
  • Type — normal, extreme, or exceptional

For a program that accepts a score between 0 and 100, a test plan might look like this:

TestDescriptionTest dataExpected resultType
1Typical valid score50Score accepted, result displayedNormal
2Typical valid score75Score accepted, result displayedNormal
3Minimum valid score0Score accepted, result displayedExtreme
4Maximum valid score100Score accepted, result displayedExtreme
5One below minimum-1Error message: "Invalid score"Exceptional
6One above maximum101Error message: "Invalid score"Exceptional
7Wrong data type"hello"Error message: "Invalid score"Exceptional

Notice that exceptional test data includes values just outside the range (−1, 101) as well as completely wrong data types ("hello"). The values just outside the boundary are particularly important — they test whether the validation uses >= versus > correctly.

Worked examples

Example 1 — Identifying the three error types

Three code snippets are shown below. Identify the type of error in each and explain what happens when the code runs.

1
Code A — what type is this?
name = input("Enter your name: ")
if len(name) > 0
    print("Hello,", name)
Syntax error. The if statement is missing its colon at the end. Python detects this before running and raises a SyntaxError. The program never starts.
2
Code B — what type is this?
age = int(input("Enter your age: "))
print(100 / (18 - age))   # if age is 18, this divides by zero
Execution error. The syntax is valid, so the program starts. But if the user enters 18, the calculation becomes 100 / 0, which raises a ZeroDivisionError and crashes the program mid-execution.
3
Code C — what type is this?
total = 0
for i in range(5):
    total = total * i   # should be total + i
print("Sum 0–4:", total)   # outputs 0, not 10
Logic error. The program runs and prints a result, but the result is wrong. total * i starts at 0×0=0 and stays 0 throughout, because any number multiplied by 0 is 0. The intended operation was total + i, which would give 0+0+1+2+3+4=10.
Example 2 — Selecting test data for a password checker

A program accepts passwords between 8 and 20 characters (inclusive) and rejects anything else. Identify appropriate normal, extreme, and exceptional test data.

1
Identify the valid range: 8 to 20 characters inclusive. This defines the boundaries for all three types of test data.
2
Choose normal test data: Typical passwords well within the range. A 12-character password and a 16-character password are both normal — they are typical inputs a real user might enter.
Expected result for both: "Password accepted".
3
Choose extreme test data: The exact boundaries — a password of exactly 8 characters and a password of exactly 20 characters. These test whether the comparison uses >= and <= (inclusive) rather than > and < (exclusive).
Expected result for both: "Password accepted".
4
Choose exceptional test data: Values outside the valid range. A 7-character password (one below minimum) and a 21-character password (one above maximum) both test the validation boundaries. An empty string (0 characters) tests a completely unusual case.
Expected result for all: "Password too short" or "Password too long" — rejected.
Example 3 — Writing a complete test plan for an age checker

A program asks the user to enter their age. If the age is between 11 and 18 (inclusive), it displays "Secondary pupil". Otherwise it displays "Not secondary age". Design a complete test plan.

1
Analyse the program: Valid range is 11 to 18. There are two outputs to test — "Secondary pupil" and "Not secondary age". The pass/fail boundary is between 10 and 11, and between 18 and 19.
2
Write the test plan — one row per test case:
TestDescriptionTest dataExpected resultType
1Typical age in range14"Secondary pupil"Normal
2Typical age in range16"Secondary pupil"Normal
3Minimum valid age11"Secondary pupil"Extreme
4Maximum valid age18"Secondary pupil"Extreme
5One below minimum10"Not secondary age"Exceptional
6One above maximum19"Not secondary age"Exceptional
7Negative number-5"Not secondary age"Exceptional
8Wrong data type"fifteen"Error / rejectedExceptional
3
Why tests 5 and 6 matter: If the programmer wrote if age > 11 instead of if age >= 11, then entering 11 (test 3) would wrongly give "Not secondary age". The extreme test catches this off-by-one logic error — normal data (14, 16) would not.
Example 4 — Finding and fixing a logic error

The code below is supposed to count how many values in the list are between 50 and 100 (inclusive), but it gives the wrong answer. Identify the logic error and fix it.

1
The buggy code:
scores = [45, 72, 38, 88, 50, 61, 100]
count = 0
for i in range(len(scores)):
    if scores[i] > 50 and scores[i] < 100:   # logic error
        count = count + 1
print("Count:", count)
2
Identify the error: The condition uses strict inequalities (> 50 and < 100). This excludes the boundary values 50 and 100 themselves. Tracing with the array: 45? No. 72? Yes (count=1). 38? No. 88? Yes (count=2). 50? No — 50 is not >50. 61? Yes (count=3). 100? No — 100 is not <100. Output: Count: 3. But the correct answer is 5 (72, 88, 50, 61, 100 all fall in the range 50 to 100 inclusive).
3
Fix: Change > to >= and < to <= to make the boundary inclusive:
if scores[i] >= 50 and scores[i] <= 100:   # corrected
Trace with fix: 45? No. 72? Yes (count=1). 38? No. 88? Yes (count=2). 50? Yes (count=3). 61? Yes (count=4). 100? Yes (count=5). Output: Count: 5. Verified ✓
Now you try

A program asks the user to enter a score out of 10. If the score is 7 or above, it displays "Pass". If the score is between 1 and 6 (inclusive), it displays "Fail". If the score is outside 1 to 10, it displays "Invalid score".

Answer the following:

  1. Identify one normal, two extreme, and two exceptional test values for this program. For each, state the expected result.
  2. The programmer wrote if score > 7 instead of if score >= 7. What type of error is this? What wrong output would entering 7 produce?
  3. Design a test plan with at least six rows covering all three types of test data.
  1. Normal: 8 → "Pass" (typical value, well within the pass range).
    Extreme: 1 → "Fail" (minimum valid score); 10 → "Pass" (maximum valid score).
    Exceptional: 0 → "Invalid score" (one below minimum); 11 → "Invalid score" (one above maximum).
  2. Logic error. The program runs without crashing, but when the user enters 7, the condition score > 7 evaluates to False (since 7 is not strictly greater than 7). The program falls through to the next condition and outputs "Fail" instead of the correct "Pass". There is no error message from Python — it is a silent wrong answer.
  3. TestDescriptionTest dataExpected resultType
    1Typical pass score8"Pass"Normal
    2Typical fail score4"Fail"Normal
    3Minimum valid score1"Fail"Extreme
    4Maximum valid score10"Pass"Extreme
    5Pass/fail boundary7"Pass"Extreme
    6One below minimum0"Invalid score"Exceptional
    7One above maximum11"Invalid score"Exceptional
    8Wrong data type"ten""Invalid score" or crash if no validationExceptional
Common mistakes
Confusing execution errors and logic errors. The key distinction is whether the program crashes. An execution error always produces a visible error message and stops the program. A logic error lets the program run to completion — the only sign something is wrong is that the output is incorrect. Never say a program has an "execution error" if it runs but gives a wrong answer.
Confusing extreme and exceptional test data. Extreme data sits at the edge of the valid range and should be accepted by the program. Exceptional data is outside the valid range and should be rejected. For a range of 1–10, the value 1 is extreme (valid boundary), while the value 0 is exceptional (invalid). Mixing these up is one of the most common errors in N5 test questions.
Only testing with normal data. A test that only uses typical values in the middle of the range will miss errors that only appear at the boundaries. A classic example: testing a "valid from 1 to 10" program with inputs 3, 5, and 7 will never reveal whether 1 and 10 are correctly included or excluded. Always include at least two extreme values in a test plan.
Writing expected results after running the program. A test plan must state the expected result — the result you predict the program should give — before running the test. If you run the program first and write down whatever it outputs, you are not testing: you are just recording what the program does. The expected result must come from your own logic, not from the program's output.
Thinking syntax errors are found at runtime. Syntax errors are detected by Python before the program runs — during the parsing phase. The program never starts. This is different from execution errors, which only appear once the program is running. If a program starts but then crashes, it is an execution error, not a syntax error.
Exam tip

Error type questions are a staple of the N5 SDD paper. The three command words used are "identify", "describe", and "explain". "Identify" just needs the name (syntax / execution / logic). "Describe" needs the name plus what happens (e.g. "a syntax error means the program will not run"). "Explain" needs the name, what happens, and why — including a reference to the code shown in the question.

For test data questions, always give specific values — not just descriptions. "A number within the range" is not acceptable; "the value 50" (for a 0–100 program) is. The expected result must also be specific: "the program displays 'Score accepted'" rather than just "it works". Vague answers lose marks even if the idea is correct.

The pass/fail boundary is always worth a test case. In a question about test plans, do not just include two extreme values (minimum and maximum of the valid range) — also test the exact value where the output changes. For a "score ≥ 7 is a pass" program, this means testing 7 (should pass) and 6 (should fail). These boundary tests are worth dedicated rows in your test plan.

Task Set

Questions 1–5 are auto-checked. Questions 6–10 are self-marked — write your answer, then reveal the model answer to check your work.

1. Which type of error causes a program to crash during execution after it has already started running? TYPE 1

2. A program accepts temperatures from −20°C to 50°C. Which value is extreme (boundary) test data? TYPE 1

3. Which of the following is an example of a syntax error? TYPE 1

4. A program accepts PINs that are exactly 4 digits (1000 to 9999). A tester enters the value "abcd". What type of test data is this? TYPE 1

5. The following Python code runs without any error message, but the output is wrong. What type of error does it contain?

price = 12.50
discount = 0.10
final = price + discount   # should subtract, not add
print("Final price: £", final)
TYPE 1

6. The code below is meant to check whether a number entered by the user is positive (greater than zero). Identify the type of error, describe what happens when the user enters 0, and write the corrected code.

number = int(input("Enter a number: "))
if number > 0:
    print("Positive")
elif number = 0:    # error here
    print("Zero")
else:
    print("Negative")
TYPE 2

Error type: Syntax error. The line elif number = 0: uses a single equals sign (=) for assignment inside a condition. Python requires == (double equals) for comparison. Python raises a SyntaxError before the program runs at all.

What happens when the user enters 0: Nothing — the program never runs. Python flags the syntax error immediately and refuses to execute any code.

Corrected code:

number = int(input("Enter a number: "))
if number > 0:
    print("Positive")
elif number == 0:    # corrected: == for comparison
    print("Zero")
else:
    print("Negative")

7. Explain the difference between an execution error and a logic error. For each, give one specific example in Python and state what the programmer must do to fix it. TYPE 2

Execution error: Occurs while the program is running — the syntax was correct so the program started, but something goes wrong during execution and the program crashes with an error message.
Example: names = ["Ali", "Sam"]; print(names[5]) — this raises an IndexError because index 5 does not exist in a 2-element list. Python displays the error and stops.
Fix: Check array bounds before accessing, or use a try/except block to catch the error.

Logic error: The program runs fully from start to finish without crashing, but the output is incorrect. Python gives no error message — the code is valid, just wrong in its reasoning.
Example: average = total / 5 when there are actually 6 values — the program runs fine but the average is wrong.
Fix: Trace through the code step by step with known test data to locate where the calculation goes wrong, then correct the logic.

8. A program asks a pupil to enter the number of hours they revised, which must be between 0 and 8 (inclusive). Identify appropriate normal, extreme, and exceptional test data for this program. For each value, state the expected result. TYPE 2

Normal test data (typical, valid values within the range):
3 → accepted, displays result. 5 → accepted, displays result.

Extreme (boundary) test data (at the exact edges of the valid range):
0 → accepted (the minimum valid input). 8 → accepted (the maximum valid input).

Exceptional test data (outside the valid range or wrong type):
−1 → rejected, error message "Invalid hours". 9 → rejected, error message "Invalid hours". "two" → rejected (wrong data type — cannot convert to an integer).

Note: the values −1 and 9 are just outside the boundaries (one below minimum, one above maximum) — these are the most important exceptional values because they test the validation condition directly.

9. The program below is supposed to count how many scores in the list are 50 or above, but the output is wrong. Identify the type of error, trace through the code to show the incorrect output, and write the corrected code with a trace to verify the fix.

scores = [45, 55, 70, 38, 50, 82]
count = 0
for i in range(len(scores)):
    if scores[i] > 50:    # error: should be >= 50
        count = count + 1
print("Count:", count)
TYPE 3

Error type: Logic error — the program runs without crashing, but gives the wrong count because the boundary value 50 is excluded by > instead of >=.

Trace of buggy code: i=0: 45>50? No. i=1: 55>50? Yes (count=1). i=2: 70>50? Yes (count=2). i=3: 38>50? No. i=4: 50>50? No — 50 is not strictly greater than 50, so it is excluded. i=5: 82>50? Yes (count=3). Output: Count: 3. But the correct answer is 4 (55, 70, 50, 82 are all ≥ 50).

Corrected code:

scores = [45, 55, 70, 38, 50, 82]
count = 0
for i in range(len(scores)):
    if scores[i] >= 50:    # corrected: >= includes the boundary value
        count = count + 1
print("Count:", count)

Trace of corrected code: i=0: 45≥50? No. i=1: 55≥50? Yes (count=1). i=2: 70≥50? Yes (count=2). i=3: 38≥50? No. i=4: 50≥50? Yes (count=3). i=5: 82≥50? Yes (count=4). Output: Count: 4. Verified: 55, 70, 50, 82 are all ≥ 50 ✓

10. Design a complete test plan for the following program. Include at least 7 test cases covering all three types of test data. For each test, give the test number, a description, the test data, the expected result, and the type (normal, extreme, or exceptional).

The program: asks the user to enter a number of items to order (valid range: 1 to 99). If valid, it displays "Order placed". If invalid, it displays "Please enter a number between 1 and 99". TYPE 3

TestDescriptionTest dataExpected resultType
1Typical quantity in range10"Order placed"Normal
2Typical quantity in range50"Order placed"Normal
3Minimum valid quantity1"Order placed"Extreme
4Maximum valid quantity99"Order placed"Extreme
5One below minimum0"Please enter a number between 1 and 99"Exceptional
6One above maximum100"Please enter a number between 1 and 99"Exceptional
7Negative number−5"Please enter a number between 1 and 99"Exceptional
8Wrong data type"ten""Please enter a number between 1 and 99" (or execution error if no type check)Exceptional

Key points: Tests 3 and 4 are the most important extreme tests — they check that 1 and 99 are included in the valid range (not accidentally excluded by > or <). Tests 5 and 6 (values just outside the boundary) are crucial for catching off-by-one logic errors. Test 8 (wrong data type) checks whether the program handles non-integer input gracefully.

Teacher notes — Shift+T to hide

Suggested timing: 60 minutes. Warm up 8 min; notes 15 min (use the summary table on the board); worked examples 12 min; now you try 8 min; task set 17 min.

Key misconception to address: Pupils almost always confuse extreme and exceptional. Reinforce with the mnemonic: Extreme = Edge (still valid, still accepted); Exceptional = Excluded (outside the range, must be rejected). Ask pupils to predict: for a range of 1–10, is the value 10 extreme or exceptional? (Extreme — it is the maximum valid value.) What about 11? (Exceptional — just outside the range.)

Live demo suggestion: Run the logic error from Example 4 live. Show the code, ask pupils to predict the output before running. When the wrong answer appears, ask them to trace through it to find the error before revealing the fix. This models the debugging process explicitly and shows why test plans with expected results are essential — you only notice the error because you knew the correct answer in advance.

Assignment link: Test plans appear in the N5 assignment (SDD task 1). Pupils who have practised writing structured test plan tables here will find the assignment section much less daunting. Emphasise that the expected result column must be filled in before running tests — markers check this.

Extension question: Ask pupils to write Python code for a simple number validator (e.g., accepts 1–10), then swap programs with a partner. Each pupil executes the test plan on their partner's program and reports whether the actual results match the expected results. This turns Q10 from a paper exercise into a practical debugging task.

SQA command words covered: "identify" (Q1, Q3, Q4, Q5, Q6, Q9), "describe" (Q7), "explain" (Q7), "design" (Q8, Q10). The exam frequently pairs "identify the type of error" with "describe what happens" — ensure pupils practise answering both parts.