3 / 0
ZeroDivisionError: division by zero
All happy families are alike, but every unhappy family is unhappy in its own way.
– Leo Tolstoy, Anna Karenina, 1878.
There are two ways that programs can succeed of fail:
They can meet or not meet the motiving requirements for the code.
Does it do what is is supposed to do?
Does it add value to a process?
Is it ethical? Does it do harm?
They can just work or not work:
Does the run at all?
Is the code brittle or buggy?
Is it sensitive to contextual factors like bad user input or a bad database connection?
Does the code work but produce incorrect outcomes?
These lead to two practices:
Strategic: Project management
Tactical: Testing and debugging
Today, we will focus on some concepts and tools for the latter.
We want to know whether our code is working or not.
We want to know if our code is of a high quality.
Approaches to quality
Through good design
Throigh principled testing
Some design principles
Don’t confuse the easy with the simple!
Invest in data design to simplify algorithm design
Write literate code
Given a choice, use tools that are documented and have a user community
Many other things :-)
Some testing activities
Syntax checking
Debugging
Ad hoc testing
Unit-testing
User testing
Some concepts
Beware of silent failures
Testing for the absence of bugs is impossible
Like Popper’s concept of falsifiability: We can never prove a scientific hypothesis to be true, we can only falsify.
Programming languages offer many tools for testing.
These include mechanisms to define and capture errors in code.
We call these errors exceptions.
We call we call dealing with these errors exception handling.
Exception handling is the foundation of unit testing.
Exceptions
A Python object that represents an error
Objects that classify kinds of errors
Coders can define these
Exception Handlers
Control structures and functions to deal with exceptions
try
/except
raise
assert
Unit Testing Frameworks
Robot
PyTest
Unittest
DocTest
Nose2
Testify
A common technical interview question: Explain the difference between an error and an exception.
An error is a serious problem that a reasonable application should not try to catch.
An error will stop execution. It means the code if broken and needs to be rewritten.
An exception is an issue that is expected or known to occur (e.g., division by zero).
Software must handle exceptions. For example, one bad row of data shouldn’t bring down the application.
Python comes with a number of predefined exceptions.
Libraries introduce new ones, too.
They used in raise
statement by the authors of programs when a possible error is anticipated.
Here are some common examples.
ZeroDivisionError
3 / 0
ZeroDivisionError: division by zero
Syntax Error
## if-statement missing colon at end
if x > 0
print("uh oh")
SyntaxError: expected ':' (223904042.py, line 3)
NameError
## references an undefined variable
print(x)
NameError: name 'x' is not defined
IndexError
## loop goes off the end of the list
= [0, 1, 2]
lst
for i in range(4):
print(lst[i])
0
1
2
IndexError: list index out of range
try/except
We use try/except
blocks to handle exceptions in our code.
These blocks work as follows: * The try
block will contain a statement. * If the statement fails, the the flow goes to the except
block. * The exception block will have code to handle the error, rather than halting the program.
The process is very similar to if/then
: * If there is an error, then raise an excpetion.
Multiple except
statements may be given, to handle specific exceptions.
Below, we give a catch-all except
for any kind of exception.
An excpetion is a mistake that will halt the execution of code. * For example, trying to access a dictionary key that does not exit. * Or, a database connection fails. * Very often, execeptions are caused by an external dependency failing, such as another system or the user.
Let’s try referencing a variable that doesn’t exist with no exception handling.
print(a)
NameError: name 'a' is not defined
Now let’s try referencing a variable that doesn’t exist with a try/except
block to handle the exception.
try:
print(a)
except:
print("caught an exception")
caught an exception
Another to catch errors before they happen is to test if computations turned out as expected.
We use assert
to verify an expression is True.
- if expression is True, nothing happens. - if expression is False, Python raises an AssertionError
exception.
Assert statements have the followinf syntax:
assert Expression[, Arguments]
where [, Arguments]
denotes optional arguments.
For example, let’s say a program is expecting three arguments to be passed from the command line.
The variable num_args
counts the arguments.
= 3
num_args
assert num_args == 3, "number of arguments must be 3!"
The assert
evaluates to True, and things proceed normally without exception.
If we change num_args = 4
this will throw an AssertionError
with the provided message.
The program then stops.
= 4
num_args
assert num_args == 3, "number of arguments must be 3!"
AssertionError: number of arguments must be 3!
If the assert
is not given a message, it throws AssertionError:
= 4
num_args
assert num_args == 3
AssertionError:
try:
assert num_args == 3
except:
print("Got an error")
Got an error
try:
assert num_args == 3, "Number of arguments must be 3!"
except AssertionError as e:
print(e)
Number of arguments must be 3!
raise()
Exceptions can be raised, too.
Use this if you want to halt the program and let other code that is using this code handle it.|
try:
10/0
except:
raise ZeroDivisionError("Hey, you can't divide by zero!")
ZeroDivisionError: Hey, you can't divide by zero!
raise IndexError("Bad Index")
IndexError: Bad Index