SDD16 — Testing: Test Plans and Error Types
- I can construct a test plan that shows a program's functional requirements have been met
- I can identify and distinguish between syntax, execution, and logic errors
- I can design test data (normal, extreme, erroneous) appropriate to a given functional requirement
- I can build a test plan table with test case, test data, expected result, actual result, and pass/fail columns
- I can trace each test case back to a specific functional requirement from the analysis stage (SDD2)
- I can explain the difference between a syntax error (caught before running), an execution error (crash while running), and a logic error (runs, but produces a wrong result)
itemRecords array, which item has the lowest .stock value?Key vocabulary
Test plans and error types
Why test at all?
Testing exists to answer one specific question: does the program actually do what it was supposed to do, as identified back in SDD2's analysis stage (its functional requirements — inputs, processes, and outputs)? A program that runs without crashing is not the same thing as a program that is correct — testing is how the gap between "runs" and "correct" gets closed. A comprehensive test plan, as required at Higher, must show that every functional requirement has genuinely been checked, not just that the program was run once and seemed to work.
Building a test plan
A test plan is a table. Each row is one test case, and typically includes: a description of what is being tested (tied to a specific functional requirement), the test data used, the expected result (worked out by hand, before running the program), the actual result (what the program genuinely produced when run), and a pass/fail outcome. A test plan with only one or two rows rarely counts as comprehensive — every distinct functional requirement needs at least one test case, and most requirements need several, covering different kinds of test data.
Three kinds of test data
National 5 already introduced three categories of test data, and Higher continues to expect all three in a comprehensive test plan: normal data (a typical, valid input the program should clearly handle correctly), extreme data (a valid input right at the edge of what's acceptable — a boundary value), and erroneous data (an invalid input that should be rejected or handled safely, not silently accepted as if it were valid). Testing only with normal data is not comprehensive — it's exactly the extreme and erroneous cases that most often expose a hidden logic error.
Three kinds of error
When a test case fails, it helps to know which of three distinct kinds of error caused it. A syntax error breaks the language's own grammar rules — a missing colon, an unmatched bracket — and is caught before the program ever starts running at all, usually flagged immediately by the editor or interpreter. An execution error (also called a runtime error) happens once the program is already running, causing it to crash partway through — dividing by zero, or accessing an array index that doesn't exist. A logic error is the most dangerous of the three precisely because it produces no error message whatsoever: the program runs to completion successfully, but the result it produces is simply wrong, because the underlying logic (a comparison, a calculation, an initial value) was mistaken.
Normal, extreme, and erroneous test data were first introduced at National 5 and are carried forward unchanged at Higher — what's new here is applying them systematically inside a full, traceable test plan, rather than testing a program informally with whatever data happens to come to mind.
def isEven(number)
return number % 2 == 0total / count # count is 0
ZeroDivisionErrorsmallest = 0 # should be
# array[0]Worked examples
def isEven(number)
return number % 2 == 0:) at the end — this breaks Python's grammar rules for defining a function.SyntaxError: expected ':'. This is caught immediately — the program never gets a chance to run at all, since it isn't valid Python.def calculateAverage(total, count):
return total / count
result = calculateAverage(100, 0)0 is not a valid mathematical operation.ZeroDivisionError: division by zero. The program crashes while running, not before — this is an execution error, not a syntax error.findMinimum misconception — initialising smallest to 0 instead of array[0]:
def findMinimumBuggy(array):
smallest = 0
for i in range(len(array)):
if array[i] < smallest:
smallest = array[i]
return smallest
stockLevels = [30, 45, 20, 15, 60, 18]
result = findMinimumBuggy(stockLevels)smallest. It runs to completion successfully.result is 0 — confirmed by an actual Python run — even though 0 does not appear anywhere in stockLevels and the true minimum is 15. No error message is ever produced; this is a logic error, and the only way to catch it is by checking the result against a hand-worked expected value, exactly what a test plan is for.calculateChange(amountPaid, price) against normal, extreme, and erroneous data:| Test data type | Test data | Expected result | Actual result | Pass/Fail |
|---|---|---|---|---|
| Normal | calculateChange(100, 75) | 25 | 25 | Pass |
| Extreme (exact payment) | calculateChange(75, 75) | 0 | 0 | Pass |
| Erroneous (insufficient payment) | calculateChange(50, 75) | -25, or a rejection? | -25 | Fail? |
-25 without crashing, but whether a negative change value counts as a "pass" or a "fail" depends entirely on whether SDD2's original functional requirements said anything about handling insufficient payment. If they didn't, this test case has just revealed a genuine gap in the requirements themselves, not just a bug in the code.
itemNames[10] when itemNames only has 6 elements; (c) using > instead of < in a minimum-finding algorithm.
- Testing only with normal data. A test plan using only typical, valid inputs is not comprehensive — extreme and erroneous cases are where hidden logic errors are most often found.
- Confusing execution errors with logic errors. If the program crashes with an error message, it's an execution error; if it runs to completion but the answer is wrong, it's a logic error — the presence or absence of a crash is the deciding factor.
- Not tracing test cases back to functional requirements. A test plan should make clear which requirement each test case is checking — an untraceable list of "things that were tried" is not the same as a comprehensive test plan.
- Assuming "it ran without crashing" means "it's correct". This is precisely the trap logic errors exploit — the expected result must always be worked out independently, by hand, before the program is run.
- Leaving the "expected result" column blank or filling it in after running the program. Expected results must be decided in advance — working them out afterwards to match whatever the program produced defeats the entire purpose of testing.
When asked to identify an error type from a code snippet or description, check first whether the program would even start running (syntax error), then whether it would crash partway through (execution/runtime error), and only then consider whether it produces a wrong-but-uncrashed result (logic error) — working through the three in this order avoids second-guessing. When designing test data, always state explicitly which category (normal, extreme, erroneous) each test case belongs to — examiners check for this classification, not just the data itself.
Task Set A — Core questions
calculateAverage(100, 0) raises ZeroDivisionError. What kind of error is this?-5 as input. Which category of test data does this represent?findMinimumBuggy(stockLevels) (with smallest wrongly initialised to 0) returns 0 instead of the true minimum 15, with no error message. What kind of error is this?calculateChange(amountPaid, price), stating the test data and expected result for each. (4 marks)Task Set B — Extension
findMinStockRecord and countLowStock functions were built without an explicit test plan. Design two edge-case test cases that would be worth adding for these functions specifically.Higher Computing Science → Software Design & Development → SDD16
Double period. First lesson of the Testing/Evaluation portion of the course — the entire Implementation strand (SDD5–SDD15) is now complete.
Suggested timing: 5 min warm-up + vocab · 25 min notes + error-grid/N5-recap walkthrough · 35 min examples 1–4 including the deliberately ambiguous Example 4 row 3 (discuss as a class before revealing "the answer") · 10 min "now you try" · 40 min Task Set A · Task Set B as homework/extension.
Key misconception: pupils often think "the program ran without an error message" is sufficient evidence of correctness — Example 3 (the SDD14 logic-error callback) and Task Set A Q8 both deliberately reuse the corrected SDD14 bug to hammer this home, since it's a real bug this course itself only caught by cross-checking against an actual Python run.
Example 4's third test-plan row is intentionally left ambiguous (marked "Fail?") — this is a genuine teaching opportunity about traceability back to SDD2's functional requirements, not a mistake in the lesson. Use it to discuss: what should happen if a functional requirement was never actually specified for this case?
SQA command words covered: explain, describe, design (test data/test cases), identify (error type).