Testing is the process of running a program to try and ascertain whether it works as intended. The purpose of testing is to show that bugs exist, not to show that a program is bug-free.
Debugging is the process of trying to fix a program that you already know does not work as intended.
- Testing and debugging should not be an afterthought.
- Good programmers design programs for easier testing and debugging.
- Break programs into separate components for independent testing.
Testing/Validation | Debugging |
---|---|
Compare input/output pairs to specifications. | Study events leading up to an error. |
"It's not working!" | Ask “Why is it not working?” |
Ask “How can I break my program?” | Find ways to fix the program. |
-
The term "debugging" is linked to a 1947 event when a moth was found in the Mark II Aiken Relay Calculator at Harvard.
-
The phrase "First actual case of bug being found" was recorded in a lab notebook.
-
Grace Murray Hopper clarified that "bug" was already in use to describe electronic system issues.
-
The term dates back to an 1896 electrical handbook mentioning "bug" for faults in electric apparatus.
- Bugs are programmer mistakes: Bugs do not appear randomly; they result from coding errors.
- Intermittent vs. persistent bugs:
- Persistent bugs appear every time under the same conditions.
- Intermittent bugs appear inconsistently, making them harder to detect.
- Overt vs. covert bugs:
- Overt bugs are obvious (crashes, slow execution, errors).
- Covert bugs are hidden and may provide incorrect outputs silently.
- Bugs that appear inconsistently can be misleading and dangerous.
- Example: An air traffic control system that mostly works but occasionally miscalculates positions can be catastrophic.
- Harder-to-reproduce bugs are difficult to identify and fix.
- Aim for bugs that are overt and persistent to ensure they are detected and fixed early.
- Defensive programming strategies:
- Use assertions and logging to catch bugs early.
- Design with fail-fast principles to make errors clear.
- Encourage clear and modular code for easier debugging.
- Covert bugs can go undetected for long periods:
- Financial software miscalculations can impact entire economies.
- Aviation software errors can cause flight malfunctions.
- Medical software inaccuracies can affect patient treatment.
- Such issues highlight the critical importance of rigorous software testing.
- Debugging is a learned skill, not an instinct.
- Transferable skill applicable to other complex systems (e.g., lab experiments, medical diagnosis).
- Debugging tools exist but mindset and approach matter more.
- Many experienced programmers rely on print statements over debugging tools.
- Ensure the language system can run the program.
- Eliminate syntax errors and static semantic errors.
- If struggling with syntax, practice with small programs first.
- Debugging starts when testing reveals unexpected behavior.
- Ask “How did I get the unexpected result?”
- The goal is to find an explanation for the problem and identify if it's part of a pattern.
- A systematic approach is key to effective debugging.
- Analyze test results and inspect the program code.
- Compare failing test cases with successful ones to find patterns.
- Accept that if a bug exists, there’s something you don’t fully understand—your goal is to uncover it.
- Develop a hypothesis that explains all observed data.
- Hypotheses can be:
- Narrow: “Changing
x < y
tox <= y
will fix the bug.” - Broad: “Aliasing issues are causing unexpected behavior.”
- Narrow: “Changing
- Conduct repeatable experiments to validate or refute your hypothesis.
- Use the simplest possible input to isolate the problem.
- Example: Add print statements before and after loops to track infinite loops.
- Plan ahead how to interpret results and avoid confirmation bias (seeing only what you expect).
- Document changes, tests, and results to avoid repeating the same ineffective solutions.
- Prevent wasting hours debugging the same issue multiple times.
- Remember: "Insanity is doing the same thing over and over again and expecting different results."
Debug systematically, not randomly.
Question assumptions and verify findings.
Maintain a logical approach and document progress.
- Python Tutor
- Print Statements
- Breakpoints & Debugging Tools in IDLE & Anaconda
- Just be systematic
- A simple and effective way to test hypotheses about code behavior.
- At the beginning of a function to indicate entry.
- To display function parameters.
- To check function results.
- Insert a print statement at the midpoint of the code.
- Use the output to determine which half contains the potential issue.
- Continue narrowing down until the bug is located.
Error Type | Cause | Example |
---|---|---|
IndexError | Trying to access an element at an invalid index. | numbers = [10, 20, 30] then numbers[5] |
TypeError | Using an operation on an incompatible data type. | len(42) , int(text) |
NameError | Attempting to use a variable that has not been defined. | print(result) (when result was never assigned) |
TypeError | Performing operations between mismatched data types. | 5 / "hello" |
SyntaxError | Writing code with incorrect syntax, such as missing brackets or quotes. | message = "Hello print(message) |
❌ Don't | ✅ Do |
---|---|
Write the entire program before testing. | Develop and test one function at a time. |
Test the entire program at once. | Test and debug each function separately. |
Debug the entire program at once. | Perform integration testing after verifying individual functions. |
- Resist the temptation to immediately start coding a fix.
- The goal is not just to fix one bug but to work towards a bug-free program.
- Consider whether this bug explains all observed issues or if it’s part of a larger problem.
- Does the bug result from mutating a list?
- Consider making a copy of the list or using a tuple instead.
- Will the fix introduce new issues or excessive complexity?
- Can the fix improve or tidy up other parts of the code?
❌ Don't | ✅ Do |
---|---|
Modify code without tracking changes. | Create a backup before making changes. |
Try to remember where the bug was. | Add comments noting potential issues. |
Test the code without documenting changes. | Keep track of modifications and compare versions. |
Forget what changes were made. | Use version control or save checkpoints. |
Panic when things break. | Stay calm and follow a structured debugging process. |
- Ensure you can revert changes if needed.
- Store old versions of the program to avoid losing progress.
- If many unexplained errors persist:
- Consider restructuring the program.
- Evaluate if a simpler algorithm could be more reliable.
- Common Mistakes to Check First:
- Passed arguments in the wrong order.
- Misspelled a variable or function name.
- Failed to reinitialize a variable.
- Used
==
to compare floating-point values instead of checking for near equality. - Tested for value equality instead of object equality (
L1 == L2
vs.id(L1) == id(L2)
). - Ignored side effects of built-in functions.
- Forgot to add
()
when calling a function. - Created an unintentional alias.
- Made any mistake that is typical for you—track recurring errors!
- Shift Your Mindset:
- Stop asking why the program isn’t working as expected.
- Instead, ask why the program is doing what it is doing.
- The bug is likely not where you think it is.
- Eliminate possibilities systematically (Sherlock Holmes' method:
“Eliminate all other factors, and the one which remains must be the truth.”)
-
Don’t trust everything you read
- Documentation may be outdated or incorrect.
- Code comments might not accurately reflect behavior.
-
Write documentation instead of debugging
- Describing the issue in words can bring clarity and a fresh perspective.
-
Step away and revisit later
- Taking breaks can make debugging more efficient.
- Start debugging early to allow time for breaks when needed.
def find_max(numbers):
max_value = numbers[0]
for num in numbers:
print(f"Checking: {num}, Current Max: {max_value}") # Debug print
if num > max_value:
max_value = num
return max_value
print(find_max([3, 1, 7, 2, 8, 5]))
Output:
Checking: 3, Current Max: 3
Checking: 1, Current Max: 3
Checking: 7, Current Max: 3
Checking: 2, Current Max: 7
Checking: 8, Current Max: 7
Checking: 5, Current Max: 8
8
def divide(a, b):
return a / b # This may cause a ZeroDivisionError
# Testing: Finding potential issues
print(divide(10, 2)) # Expected output: 5.0
print(divide(5, 0)) # This causes a ZeroDivisionError
Output:
5.0
......, line 3, in divide
return a / b # This may cause a ZeroDivisionError
ZeroDivisionError: division by zero
def safe_divide(a, b):
if b == 0:
print("Error: Division by zero!")
return None
return a / b
print(safe_divide(5, 0)) # Debugged version, handles division by zero
Output:
Error: Division by zero!
None
def square_root(x):
assert x >= 0, "Cannot compute square root of a negative number"
return x ** 0.5
print(square_root(16)) # Works
print(square_root(-4)) # AssertionError
Output:
4.0
AssertionError: Cannot compute square root of a negative number
# Finding a bug in a loop using print statements
def faulty_loop():
for i in range(5):
print(f"Before update: i = {i}")
i += 2 # This won't affect the loop iteration
print(f"After update: i = {i}")
faulty_loop()
Output:
Before update: i = 0
After update: i = 2
Before update: i = 1
After update: i = 3
Before update: i = 2
After update: i = 4
Before update: i = 3
After update: i = 5
Before update: i = 4
After update: i = 6
import logging
logging.basicConfig(level=logging.DEBUG)
def add(a, b):
logging.debug(f"Adding {a} and {b}")
return a + b
result = add(5, 7)
print(result)
Output:
DEBUG:root:Adding 5 and 7
12
In this assignment, you will apply debugging techniques, use static analysis tools, and implement automated testing to improve a faulty merge sort implementation. By the end of this assignment, you will:
-
Identify and fix bugs in
hw2_debugging.py
, which uses helper functions fromrand.py
. -
Use static analysis tools to detect and correct coding issues before execution.
-
Write unit tests with pytest to validate the correctness of your merge sort.
-
Automate code formatting, static analysis, and testing in your GitHub repository.
-
Create a PDF file containing:
- Your repository link
- Group number
- Names of all participants (Name - github user id)
-
Only one group member submits the work.
-
Submit the PDF file.
- Download and unzip
hw3.zip
. - Inside
hw3.zip
, you will find:hw2_debugging.py
(contains a faulty merge sort implementation).rand.py
(provides a function to generate a random array).
You can modify files.
- Your goal is to debug, test, and improve the merge sort function. Make sure to fix logic errors to ensure the sorting algorithm functions correctly.
- Choose "number of group members" of the following:
pylint
(general static analysis and code quality)pyflakes
(error detection)bandit
(security vulnerabilities)radon
(complexity analysis)
If you have 3 group members, that means that you need to choose 3 tools. Each group member should be responsible for installing 1 tool to the repository. In step 3, you need to run all tools for your code.
The goal is to have tools running in GitHub Actions as part of the workflow.
-
Add extra code (any algorithm with issues; the algorithm should be different from your group members’). It is recommended that students implement the algorithm by hand rather than copying an example online. This approach will help reinforce the understanding of debugging techniques, which are key learning objectives of this assignment.
-
Run tools on all files:
pylint hw2_debugging.py rand.py > pylint_report.txt pyflakes hw2_debugging.py rand.py > pyflakes_report.txt
- Correct coding style violations, security concerns, and potential logical errors.
- Ensure that all fixes maintain the intended functionality.
- After fixing issues, rerun each tool and save the updated results inside a
post_traces
folder(tools output).
Make sure to address all static analysis issues.
- Guttag, J. V. Introduction to Computation and Programming Using Python. http://repo.darmajaya.ac.id/5070/1/Introduction%20to%20Computation%20and%20Programming%20Using%20Python%20by%20John%20V.%20Guttag%20%28z-lib.org%29.pdf
- Brian Marick, "How to Misuse Code Coverage"
- Ayewah, Pugh, Hovemeyer, Morgenthaler, Penix, "Using Static Analysis to Find Bugs" =======
- Total: 1 point.
- Each step is worth 0.11 points.
- Ensure all steps are completed to receive full credit.