Welcome back! In our last lesson, we focused on functions, the fundamental building blocks for creating reusable and organized code. We saw how they can take inputs, perform tasks, and return results. You also saw a brief example where a function could fail by raising an error if given incorrect input.
This lesson directly addresses that scenario. As a Quality Assurance professional, you know that a critical aspect of robust software is its ability to handle unexpected situations gracefully. A test script that crashes halfway through because an element wasn't found or an API returned an error is unreliable. This lesson introduces the mechanism Python provides to build that resilience into your code: exception handling. You will learn how to anticipate and manage runtime errors using try and except blocks, ensuring your automation scripts can handle failures intelligently instead of just stopping.
When Code Goes Wrong: Syntax Errors vs. Exceptions
In Python, not all errors are the same. It's crucial to distinguish between two main types:
-
Syntax Errors: These are grammatical mistakes in your code. You might forget a colon, misspell a keyword, or have mismatched parentheses. The Python interpreter detects these before your program starts running and will refuse to execute it. Your IDE, like PyCharm, will usually highlight these for you as you type.
-
Exceptions: These are errors that occur during the execution of a syntactically correct program. The code is valid Python, but a situation arises that the program cannot handle. A classic example is trying to divide a number by zero. The syntax is correct, but the operation is mathematically impossible.
The video "Exception Handling in Python" from Software Testing Mentor provides a clear, practical distinction between these two.
Python for Testers #39 - Exception Handling in Python
This video starts by explaining the difference between syntax errors and runtime exceptions using a simple division example, which is a perfect introduction to our topic.
Watch the first segment, from the beginning to this point, to see a live demonstration of a ZeroDivisionError and understand why it's considered an exception, not a syntax error.
When an exception occurs and is not handled, your program "crashes" and prints a traceback message. While this traceback is useful for debugging, it's not what you want to happen in a running automation suite. We need a way to catch these exceptions and decide what to do next.
The try and except Blocks: Catching Errors
The primary tool for handling exceptions in Python is the try...except block. The logic is straightforward:
- The
tryblock contains the code that you suspect might raise an exception. - The
exceptblock contains the code that runs if and only if an exception occurs in thetryblock.
This allows you to "catch" the error and prevent it from crashing your program. You can then log the error, attempt a recovery action, or simply mark a test as failed and move on.
The article "Python Exceptions: An Introduction" from Real Python offers a thorough explanation of this core concept.
Python Exceptions: An Introduction
This article section explains the fundamental try and except structure and, most importantly, emphasizes the best practice of catching specific exceptions.
Please read the section titled Handling Exceptions With the try and except Block. Pay close attention to the argument against using a bare except: clause. Understanding why it's better to catch specific errors like FileNotFoundError or RuntimeError is crucial for writing maintainable and debuggable test code.
As you've just read, being specific about the exceptions you catch is vital. A broad except: can hide bugs you weren't expecting. For example, in a test, you might expect an element to be "not found," but a generic except block could also hide a completely different error, like a network failure, making the root cause of the test failure difficult to diagnose.
Let's watch a clear demonstration from Corey Schafer on how to handle specific exceptions and even catch multiple different types of exceptions in a structured way.
Python Tutorial: Using Try/Except Blocks for Error Handling
This video provides excellent examples of handling FileNotFoundError and shows the syntax for layering multiple except blocks to handle different potential errors.
First, watch how a try...except block is used to handle a potential error gracefully instead of crashing. Then, watch the next segment on catching specific exceptions. This part clearly demonstrates why catching a general Exception can be misleading and how to stack except blocks for different error types, like FileNotFoundError and NameError.
The Full Structure: else and finally
The try...except block can be extended with two more optional clauses: else and finally. These give you finer control over your code's execution flow.

-
The
elseBlock: This code runs only if thetryblock completes successfully, meaning no exceptions were raised. This is useful for separating your "happy path" logic from your error-handling logic. For a test, you might put the successful validation assertions in theelseblock. -
The
finallyBlock: This code runs no matter what. It executes whether an exception occurred or not. This is extremely important for cleanup actions. In test automation, you would usefinallyto ensure that resources are released, such as closing a browser window, shutting down a database connection, or writing a report file, even if the test itself failed.
This pattern is demonstrated perfectly in the following code image.

The video from Software Testing Mentor explains the roles of else and finally with examples directly relevant to your work.
Python for Testers #39 - Exception Handling in Python
This video continues from where we left off, explaining the full try...except...else...finally structure.
First, watch the quick intro to the basic try-except syntax. Next, see the explanation of the else block, which executes only when there is no exception. Finally, watch the segment on the finally block. The instructor gives excellent examples of its use for cleanup, such as closing files or database connections, which is a key practice in robust automation.
Proactively raise-ing Exceptions
Sometimes, you may want to trigger an exception yourself based on a specific condition in your code. For instance, if a function receives invalid input, you can raise an exception to signal that something is wrong. This is clearer than returning an error code and forces the calling code to handle the problem.
Python for Testers #39 - Exception Handling in Python
This short segment shows how to use the raise keyword to create your own exceptions based on logical conditions.
Watch the section from this timestamp to see how you can check a condition (like y == 0) and manually raise an exception with a custom message.
Let's Practice
Imagine you are writing a helper function for a test suite. This function validates results from a dictionary that stores test outcomes. We need to make it robust against missing data or incorrect data types.
Your Task:
Complete the function get_final_score below. It takes a dictionary of scores and a test ID. It should:
- Try to retrieve the score for the given
test_id. - Handle a
KeyErrorif thetest_iddoesn't exist in the dictionary, printing an informative message. - Handle a
TypeErrorif the score is not a number (e.g.,Noneor a string), as we can't perform calculations on it. Print a different message for this case. - If the score is successfully retrieved and is a number, use the
elseblock to print a "Validation successful" message along with the score. - Use the
finallyblock to always print "Finished score validation attempt."
Here is the code to get you started:
def get_final_score(scores_data, test_id):
"""
Retrieves and validates a test score from a dictionary.
Handles potential KeyError and TypeError.
"""
print(f"\n--- Checking test ID: {test_id} ---")
try:
score = scores_data[test_id]
# This line will raise a TypeError if score is not a number
calculated_score = score * 1.0
except KeyError:
print(f"Error: Test ID '{test_id}' not found.")
except TypeError:
print(f"Error: Score for '{test_id}' is not a valid number. Found: {score}")
else:
print(f"Validation successful. Score: {calculated_score}")
finally:
print("Finished score validation attempt.")
# --- Data for testing ---
test_scores = {
"login_test_01": 98,
"payment_test_05": 76,
"search_test_02": "incomplete", # Invalid data type
"dashboard_test_03": None # Invalid data type
}
# --- Let's run the function with different inputs ---
get_final_score(test_scores, "login_test_01") # Success case
get_final_score(test_scores, "checkout_test_99") # Key error case
get_final_score(test_scores, "search_test_02") # Type error case
get_final_score(test_scores, "dashboard_test_03") # Type error case
Expected Output:
--- Checking test ID: login_test_01 ---
Validation successful. Score: 98.0
Finished score validation attempt.
--- Checking test ID: checkout_test_99 ---
Error: Test ID 'checkout_test_99' not found.
Finished score validation attempt.
--- Checking test ID: search_test_02 ---
Error: Score for 'search_test_02' is not a valid number. Found: incomplete
Finished score validation attempt.
--- Checking test ID: dashboard_test_03 ---
Error: Score for 'dashboard_test_03' is not a valid number. Found: None
Finished score validation attempt.
Conclusion
You have now learned how to write more resilient and robust Python code. By anticipating and handling runtime errors, you can prevent your automation scripts from crashing unexpectedly, which is a cornerstone of a reliable and professional testing framework.
Here are the key takeaways:
- Exceptions are errors that occur during program execution.
- Use a
tryblock to wrap code that might fail. - Use an
except SpecificErrorblock to catch and handle a particular type of error. Avoid genericexceptblocks. - Use an
elseblock for code that should only run if thetryblock was successful. - Use a
finallyblock for cleanup code that must run regardless of whether an error occurred.
In our next lesson, we will put this knowledge to immediate use as we learn about reading from and writing to text files. File operations are a prime example of where exception handling is not just useful, but essential. What if the file you want to read doesn't exist (FileNotFoundError)? What if you don't have permission to write to a location (PermissionError)? You are now equipped to handle these scenarios.