Unit 5: Code Testing and Debugging — Long Questions
12th Class Computer Science · Unit 5: Code Testing and Debugging
Testing and debugging are advanced techniques that go beyond basic error fixing to ensure the overall quality of an application. Testing is essential for reliable applications for the following reasons:
1. Verifying requirements: Testing checks whether an application meets its requirements and that all its functions produce correct results.
2. Reducing risk: It reduces the risk of crashes or incorrect outputs reaching the end user.
3. Protecting existing features: Testing helps verify that new changes do not break features that were already working correctly.
4. Building trust: Applications that are thoroughly tested are more trustworthy and stable, which increases user confidence.
5. Early detection: Testing and debugging together help detect and remove errors before software is released, and regular testing prevents unexpected failures during actual operation.
Role in program stability Debugging locates the exact cause of an error and fixes it efficiently, while testing confirms that the fix (and the rest of the program) still works as expected. Together, testing and debugging increase reliability, make future maintenance easier, and improve both performance and user satisfaction, so the program remains stable even as it grows or is modified over time.
During software development, several common types of programming errors and bugs can occur:
1. Syntax Error: Occurs when code does not follow the rules of the programming language, such as a missing colon. The program fails to run at all.
Example:
if x > 5
print("x is greater")
This raises SyntaxError: invalid syntax. The correct version is:
if x > 5:
print("x is greater")
2. Logic Error: Occurs when the program runs without crashing but produces an incorrect result because the underlying formula or logic is wrong.
Example:
length = 5
width = 3
area = length + width # incorrect logic
print(area) # prints 8, which is wrong
The correct logic uses multiplication: area = length * width.
3. Runtime Error: Occurs while the program is executing, when an invalid operation is attempted, causing the program to stop.
Example:
a = 10
b = 0
result = a / b
This raises ZeroDivisionError: division by zero.
4. Other bugs: The chapter also mentions data errors and resource leaks as other common bugs that can affect an application.
Identifying which of these error types is occurring is key to building stable, reliable applications, since each type needs a different approach to fix (correcting syntax, correcting logic/formulas, or guarding against invalid runtime operations).
Unit testing is the process of testing individual parts or units of a program, such as single functions or methods, to ensure they work correctly on their own. It helps catch errors early, makes debugging easier, and checks whether each component produces its expected output. Writing clear and simple test cases improves overall code reliability.
Python provides two popular modules for unit testing:
1. unittest: This is built into Python and provides a framework for creating test cases, usually organized as classes with test methods.
2. pytest: This is an external module that simplifies writing and running tests. It does not require test case classes or boilerplate code — tests are written as simple functions (usually starting with "test_"), and the assert statement is used to check whether the output is correct. Pytest automatically detects and runs these test functions, and tests are executed simply by running pytest in the command line in the directory where the file is located.
Example using pytest
def add(a, b):
return a + b
def multiply(a, b):
return a * b
def testadd():
assert add(2, 3) == 5
def testmultiply():
assert multiply(2, 3) == 6
Both modules help verify that code behaves as expected, can test individual functions, classes, or entire modules, and improve program stability while reducing errors in larger applications. Repeated, automated testing also ensures that later changes to the code do not break existing functionality.
A test case defines specific inputs for a function along with the expected output. The process of writing and executing test cases is as follows:
1. Write the function to be tested: First, the actual function (the code under test) is written normally.
2. Write test functions: In pytest, test functions are written as simple functions whose names usually start with "test_". No special class or boilerplate code is required.
3. Use assert to check correctness: Inside each test function, the assert statement compares the function's actual output with the expected output. If they match, the test passes; if not, assert raises an error and the test fails.
4. Run the tests: Pytest automatically detects all functions starting with "test_" in the file. The tests are executed by simply running the command "pytest" in the terminal, in the directory where the test file is located.
5. Review results: Python/pytest reports which tests passed and which failed or raised errors, helping the programmer immediately see where a problem exists.
Example
def add(a, b):
return a + b
def multiply(a, b):
return a * b
def testadd():
assert add(2, 3) == 5 # passes because 2 + 3 = 5
def testmultiply():
assert multiply(2, 3) == 6 # passes because 2 * 3 = 6
Repeating this test execution after every code change ensures that new changes do not break existing functionality, and automated test execution saves time and improves accuracy during development.
Breakpoints and watches are tools used in debugging to analyse program behaviour by pausing execution and examining data at specific points, making debugging faster and more precise.
Breakpoints A breakpoint is a marker placed at a chosen line of code that pauses program execution when that line is reached. IDEs like PyCharm and VS Code allow a breakpoint to be set with a simple click in the margin next to the line number. Once the program pauses at a breakpoint, the programmer can examine the current state of the program (variable values, call stack, etc.) without having to run or trace through the entire program. This helps the programmer focus only on the specific section of code suspected of containing an error.
Watch Expressions A watch expression tracks the value of a chosen variable or expression while the program runs, and it updates automatically whenever that value changes. This lets the programmer observe, step by step and in real time, how data changes as the program executes, which helps identify incorrect or unexpected values.
Example
In the function
def calculatearea(radius):
area = 3.14 * radius ** 2
return area
a breakpoint can be placed inside calculatearea() to pause and check the value of radius, and a watch expression can be added for radius to observe it change from 5 to 10 as main() calls the function twice with different values.
Together, breakpoints and watch expressions let the programmer pinpoint exactly where and why a variable takes an unexpected value, making it much easier to locate and fix bugs than reading the code alone.
Setting breakpoints In PyCharm (and similarly in VS Code), a breakpoint is set by clicking the line number next to the code in the left margin of the editor. A small marker (usually a red dot) appears at that line to show a breakpoint has been placed. When the program is then run in debug mode and execution reaches that line, the program automatically pauses, and the current state of the program — such as the values of all local variables — can be inspected.
Example
Consider the program
def calculatearea(radius):
area = 3.14 * radius ** 2
return area
def main():
radius = 5
print("Area of circle:", calculatearea(radius))
radius = 10
print("Area of circle:", calculatearea(radius))
if _name_ == "_main_":
main()
Breakpoints can be set at the line radius = 5, at the line radius = 10, and inside calculatearea() to pause execution at each of those points and inspect the value of radius and the calculated area.
Significance in debugging Breakpoints allow the programmer to focus on a specific section of code without having to run or trace through the entire program from start to finish. By pausing execution exactly where a problem is suspected, the programmer can check whether variables hold the expected values at that point, which makes it much faster and easier to find the exact location of a bug. Breakpoints are often combined with watch expressions (to continuously monitor a variable's value) and step-by-step execution (Step Into, Step Over, Continue) to trace the program's logic precisely, as shown in PyCharm's debugging interface (Figure 5.2).
Step-by-step debugging is a debugging technique in which code is executed one line or one function at a time, instead of running the whole program at once. It allows the programmer to follow the program's logic as it actually executes and to detect precisely where an error occurs.
In IDEs such as PyCharm, step-by-step debugging offers three main actions once execution is paused at a breakpoint:
1. Step Into: Goes inside a called function to see exactly how it works internally (for example, stepping into calculatearea() to see how the area is calculated).
2. Step Over: Executes the current line completely and moves to the next line, without entering any function that line calls (for example, stepping over the line area = 3.14 * radius ** 2 to skip over the calculation details).
3. Continue: Resumes normal execution until the next breakpoint is reached.
Example
def calculatearea(radius):
area = 3.14 * radius ** 2
return area
def main():
radius = 5
print("Area of circle:", calculatearea(radius))
radius = 10
print("Area of circle:", calculatearea(radius))
if _name_ == "_main_":
main()
By stepping through main() and calculatearea() line by line and watching the value of radius and area at each step, the programmer can see exactly where a calculation or value goes wrong.
How it helps fix bugs: Because the programmer can watch the exact sequence of execution and the values involved at each step, step-by-step debugging makes it much easier to locate and fix bugs accurately compared to simply re-reading the code or running the whole program and guessing where the fault lies.
Exception handling is used to manage errors that occur during program execution. It prevents the program from crashing suddenly and allows it to respond gracefully to unexpected situations, improving reliability and user experience. Python provides built-in tools — the try, except, and finally keywords — to catch and handle exceptions efficiently.
How it works
- The try block contains the code that might raise an error.
- The except block catches a specific exception type (or types) if one occurs inside the try block, and runs code to handle it instead of letting the program crash.
- The finally block always runs, whether or not an exception occurred, and is typically used to perform clean-up actions such as closing a file or database connection.
Example import sqlite3
try:
connection = sqlite3.connect("school.db")
cursor = connection.cursor()
cursor.execute("INSERT INTO students (ID, Name, Age) VALUES (?, ?, ?)", (1, "Ali", 20))
connection.commit()
print("Data inserted successfully")
except sqlite3.Error as e:
print("Error occurred:", e)
connection.rollback()
finally:
if connection:
connection.close()
print("Connection closed")
Here, try-except handles errors and prevents crashes, commit() saves correct data to ensure integrity, rollback() undoes changes if an error occurs, and finally safely closes the database connection regardless of the outcome.
Handling multiple exception types A single program may encounter different kinds of exceptions (for example, a ValueError from bad input and a ZeroDivisionError from dividing by zero). Python allows catching multiple exception types within one try-except structure, either using separate except blocks for each exception type or by grouping exception types together, so that each type of error can be given its own specific response. This approach prevents the program from crashing unexpectedly on any single type of error and ensures proper, controlled error recovery, which improves overall program stability.