Debugging and Testing: Finding and Preventing Errors
What is Debugging?
Debugging is the process of finding, analyzing, and fixing errors (bugs) in your code. It's an essential skill
that helps you understand why your program isn't working as expected and how to correct it.
Types of Errors
1. Syntax Errors
Code that violates the language's grammar rules:
python
# Missing colon
if x > 5
print("Greater than 5") # SyntaxError
# Incorrect indentation
def my_function():
print("Hello") # IndentationError
# Mismatched parentheses
print("Hello World" # SyntaxError: missing closing parenthesis
2. Runtime Errors (Exceptions)
Errors that occur while the program is running:
python
# Division by zero
result = 10 / 0 # ZeroDivisionError
# Accessing non-existent list index
my_list = [1, 2, 3]
print(my_list[5]) # IndexError
# Wrong data type
number = int("hello") # ValueError
# Undefined variable
print(undefined_var) # NameError
3. Logic Errors
Code runs without crashing but produces incorrect results:
python
# Intended to calculate average, but logic is wrong
def calculate_average(numbers):
total = sum(numbers)
return total / (len(numbers) + 1) # Should be len(numbers)
# Using wrong comparison operator
age = 25
if age = 18: # Should use == for comparison
print("Eligible to vote")
Debugging Strategies
1. Print Debugging
Add print statements to trace program execution:
python
def find_maximum(numbers):
print(f"Input: {numbers}") # Debug print
if not numbers:
print("Empty list detected") # Debug print
return None
max_val = numbers[0]
print(f"Initial max: {max_val}") # Debug print
for i, num in enumerate(numbers[1:], 1):
print(f"Checking index {i}: {num}") # Debug print
if num > max_val:
max_val = num
print(f"New max: {max_val}") # Debug print
return max_val
2. Rubber Duck Debugging
Explain your code line-by-line to an imaginary listener (or rubber duck). This often helps you spot the
problem yourself.
3. Binary Search Debugging
Comment out half of your code to isolate where the problem occurs:
python
def problematic_function():
# Step 1: Working fine
setup_data()
# Step 2: Comment this section to test
# process_data()
# transform_data()
# Step 3: Working fine
display_results()
4. Using Debugger Tools
Most IDEs provide debugging tools:
Breakpoints: Pause execution at specific lines
Step Over: Execute one line at a time
Step Into: Go inside function calls
Watch Variables: Monitor variable values
Exception Handling
Try-Except Blocks
Handle errors gracefully instead of crashing:
python
# Basic exception handling
try:
user_input = input("Enter a number: ")
number = int(user_input)
result = 100 / number
print(f"Result: {result}")
except ValueError:
print("Please enter a valid number")
except ZeroDivisionError:
print("Cannot divide by zero")
except Exception as e:
print(f"An unexpected error occurred: {e}")
Finally Block
Code that runs regardless of whether an exception occurs:
python
try:
file = open("data.txt", "r")
content = file.read()
# Process content
except FileNotFoundError:
print("File not found")
finally:
if 'file' in locals():
file.close() # Always close the file
Testing Your Code
Manual Testing
Test your functions with different inputs:
python
def add_numbers(a, b):
return a + b
# Manual tests
print(add_numbers(2, 3)) # Expected: 5
print(add_numbers(-1, 1)) # Expected: 0
print(add_numbers(0, 0)) # Expected: 0
Unit Testing (Python)
Automated testing using the unittest module:
python
import unittest
def multiply(a, b):
return a * b
class TestMultiply(unittest.TestCase):
def test_positive_numbers(self):
self.assertEqual(multiply(3, 4), 12)
def test_negative_numbers(self):
self.assertEqual(multiply(-2, 3), -6)
def test_zero(self):
self.assertEqual(multiply(0, 5), 0)
if __name__ == "__main__":
unittest.main()
Test-Driven Development (TDD)
Write tests before writing the actual code:
python
# Step 1: Write failing test
def test_is_even():
assert is_even(4) == True
assert is_even(3) == False
# Step 2: Write minimal code to pass
def is_even(number):
return number % 2 == 0
# Step 3: Refactor if needed
Common Debugging Techniques
Input Validation
Check inputs before processing:
python
def divide_numbers(a, b):
# Validate inputs
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("Both arguments must be numbers")
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
Logging
Better than print statements for production code:
python
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
def process_data(data):
logger.debug(f"Processing data: {data}")
if not data:
logger.warning("Empty data received")
return []
logger.info("Data processing completed successfully")
return processed_data
Assertions
Check assumptions during development:
python
def calculate_average(numbers):
assert len(numbers) > 0, "List cannot be empty"
assert all(isinstance(x, (int, float)) for x in numbers), "All items must be numbers"
return sum(numbers) / len(numbers)
Best Practices
Debugging
Start simple: Check obvious things first (spelling, syntax)
Use version control: Commit working code before making changes
Isolate the problem: Create minimal examples that reproduce the bug
Read error messages: They often tell you exactly what's wrong
Take breaks: Fresh eyes often spot problems immediately
Testing
Test edge cases: Empty inputs, maximum/minimum values, invalid data
Test normal cases: Typical expected inputs
Write readable tests: Clear test names and simple assertions
Keep tests independent: Each test should work on its own
Run tests frequently: Catch problems early
Common Debugging Mistakes
Changing too much at once: Make small, incremental changes
Not reading error messages: They contain valuable information
Assuming the problem is complex: Often it's something simple
Not testing after fixes: Ensure your fix actually works
Ignoring compiler/interpreter warnings: They often indicate real problems
Debugging Tools and Resources
IDE Debuggers: Visual Studio Code, PyCharm, Eclipse
Online Tools: Python Tutor (visualizes code execution)
Command Line: Python's pdb module
Browser Tools: Developer tools for web development
Effective debugging and testing skills will make you a more confident programmer and help you write
more reliable, maintainable code. Remember: bugs are learning opportunities, not failures!