Back to Topics
Term 3 DI10-5

Testing & Debugging

Learning Objectives

  • Identify different types of errors
  • Design test cases with expected outputs
  • Apply debugging strategies to find and fix errors
  • Use trace tables to track program execution

Types of Errors

Programs can have three main types of errors. Understanding each type helps you fix problems more efficiently.

Syntax Errors

Mistakes in the code's grammar. The program won't run.

print("Hello" # Missing )

Runtime Errors

Errors that occur while the program runs. Causes crashes.

x = 10 / 0 # Division by zero

Logic Errors

Program runs but gives wrong results. Hardest to find.

average = a + b / 2 # Should be (a+b)/2

Common Syntax Errors

  • Missing colons after if, for, while, def
  • Mismatched brackets or parentheses
  • Incorrect indentation
  • Misspelled keywords (pritn instead of print)
  • Missing quotes around strings

Common Runtime Errors

  • Division by zero
  • Using a variable before it's defined
  • Index out of range (accessing list item that doesn't exist)
  • Type errors (adding string to number)
  • File not found

Common Logic Errors

  • Wrong operator (= instead of ==)
  • Off-by-one errors in loops
  • Incorrect order of operations
  • Wrong condition in if statements
  • Forgetting to update a variable in a loop

Testing

Testing is the process of running your program with various inputs to check if it produces the correct outputs.

Test Cases

A test case includes the input values you'll use and the expected output. Good test cases cover:

  • Normal cases - Typical, expected inputs
  • Boundary cases - Edge values (minimum, maximum, zero)
  • Error cases - Invalid or unexpected inputs

Example: Testing a Grade Calculator

Test # Input (Score) Expected Output Type
1 85 "A" Normal
2 65 "B" Normal
3 80 "A" Boundary
4 79 "B" Boundary
5 0 "F" Boundary
6 100 "A" Boundary
7 -5 Error message Error
8 105 Error message Error

Debugging Strategies

Debugging is the process of finding and fixing errors (bugs) in your code.

1. Read the Error Message

Error messages tell you:

  • The type of error
  • The line number where it occurred
  • A description of what went wrong
Traceback (most recent call last):
  File "program.py", line 5, in <module>
    print(x)
NameError: name 'x' is not defined

# This tells us:
# - Error is on line 5
# - It's a NameError
# - The variable 'x' doesn't exist

2. Print Debugging

Add print statements to see what's happening inside your code:

# Add prints to trace execution
def calculate_average(numbers):
    print(f"Input: {numbers}")  # Debug print

    total = 0
    for num in numbers:
        total = total + num
        print(f"Running total: {total}")  # Debug print

    average = total / len(numbers)
    print(f"Final average: {average}")  # Debug print
    return average

3. Rubber Duck Debugging

Explain your code line-by-line to someone (or a rubber duck!). Often, you'll find the problem while explaining.

4. Divide and Conquer

Comment out sections of code to isolate where the error is occurring. Narrow down until you find the problematic line.

5. Check Your Assumptions

  • What type is this variable? (Use type() to check)
  • What value does it actually have? (Use print())
  • Is this condition actually True or False?
  • Is this code even being executed?

Trace Tables

A trace table tracks how variables change as a program executes, step by step. This helps find logic errors.

Example: Finding the Bug

# This code should count down from 5 to 1
# But it has a bug!

count = 5
while count > 0:
    print(count)
    count = count - 1

Let's trace it:

Step count count > 0? Output New count
Start 5 - - -
1 5 True 5 4
2 4 True 4 3
3 3 True 3 2
4 2 True 2 1
5 1 True 1 0
6 0 False - -

The trace shows the loop works correctly, printing 5, 4, 3, 2, 1 and stopping when count becomes 0.

Debugging Checklist

  1. Read the error message carefully
  2. Check the line number mentioned
  3. Look for common errors (spelling, brackets, indentation)
  4. Add print statements to see variable values
  5. Create a trace table for logic errors
  6. Test with different inputs
  7. Take a break and come back with fresh eyes
  8. Ask someone else to look at it

Key Terminology

  • Bug - An error in a program
  • Debugging - Finding and fixing bugs
  • Syntax error - Mistake in code grammar (won't run)
  • Runtime error - Error during execution (crash)
  • Logic error - Code runs but produces wrong result
  • Test case - Input values and expected output for testing
  • Trace table - Table tracking variable values through execution
  • Boundary testing - Testing edge values (min, max, zero)
🏠

Project Connection

In Simpson's House...

Testing Simpson's House is real test-driven development — you define expected outputs for specific inputs, run the system, and observe whether the hardware behaves correctly. Debugging means SSH-ing into the Pi and reading live logs, not just reading error messages on screen.

Test Cases

Input → Expected output for every device command

Test case: publish "ON" to home/light → LED turns on (GPIO pin 17 HIGH). Publish "OFF" → LED off. Publish "OPEN" to home/garage → servo moves to 90°. Each test is concrete and observable.

Boundary & Error Cases

What happens with unexpected input?

What if someone sends "on" (lowercase)? Or "TOGGLE"? Testing boundary cases reveals whether the Python code handles unexpected payloads gracefully or crashes silently.

Debugging on the Pi

SSH in and read live logs with journalctl

journalctl -u simpsons-house -f streams the program's output in real time — you can watch exactly what the Pi receives and how it responds. This is hands-on debugging.

Logic Errors

The servo goes to the wrong angle — but why?

A classic logic error: the duty cycle formula is slightly wrong, so "OPEN" moves the door to 45° instead of 90°. The code runs without crashing but produces the wrong result — you have to trace the calculation to find it.

Explore the full Simpson's House project →