Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

Introduction to testing exceptions

To make tests robust and detailed we also should check all exceptions which can occur during code execution. To  In order to write assertions about raised exceptions, you can use pytest.raises() as a context manager. For this example, we are going to write a test to check if the whether the divide() function returns an exception during a dividing number by zero or not.   In this case, all tests are written Like in the previous section all tests will be collected in the test_exceptionsoperations.py file . For the tutorial purpose, there is also created new file list_func.py which contains the extra function to test.  and all new functions will be added to the operations.py file. 

Our project folder should look like this:

Code Block
languagetext
.
└── calculator
    ├── code_examples
│  sources
    │   ├──  division_funcoperations.py
│   ├──    │   └── __init__.py
│     └── list_func.py
└── tests
        ├── __init__.py
    ├── test_division_func.py
    └── test_exceptionsoperations.py

ZeroDivisionError example

For the record division() function looks like this:

Code Block
languagepy
def divisiondivide(a, b):
    return a/b


To check is dividing by zero returns exception we are going to import Pytest and write a test that looks like this:

Code Block
languagepy
import pytest
from code_examples.division_funcsources.operations import divisiondivide


def test_division_by_zero():
    with pytest.raises(ZeroDivisionError):
        divisiondivide(1, 0)


This test uses a special context manager facility in Pytest, in which you run a block of code that you expect to raise an exception, and let pytest handle let Pytest handle it. Your test will fail if the exception is not raised. 

Let's run a Pytest and see the output. To run the only tests from the test_exceptions.py file pytest run test_division_by_zero separately, the key expression option will be used. Pytest run should look like this:

Code Block
languagetext
pytest test_exceptions.py pytest -k zero -v


Image RemovedImage Added

Test passed. As expected, the division method returns a ZeroDivisionError exception.  

...

If there is a need to have access to the actual exception info the Pytest context manager optionally lets you add add as 'as your_text' like in the example below. "'exception_info" is an ExceptionInfo instance' is an ExceptionInfo instance, which is a wrapper around the actual exception raised. The main attributes of interest are .type.value and value and .traceback.

Code Block
languagepy
import pytest
from code_examples.division_funcsources.operations import divisiondivide


def test_division_by_zero_with_exception_info():
    with pytest.raises(ZeroDivisionError) as exception_info:
        division(1, 0)
    assert "division by zero" in str(exception_info.value)

IndexError example

For the IndexError example we need to create another python function:

...

This dummy function doesn't have protection against providing a list index that is out of range (is greater than list length). To prevent that there should be a test that checks if provided index is improper range. 

Code Block
languagepy
from sources.operations import select_item_from_list


def test_select_item_from_list():
    with pytest.raises(IndexError):
        select_item_from_list([1, 2, 3], 10)

...

We expect there is no possibility to select the 10th element from the list of three. Our test passed because of the IndexError exception. 

Image RemovedImage Added

TypeError example

Moreover, there is also a change chance that arguments provided to the test_select_item_from_list function have an improper type. To verify that we are going to check if the TypeError exception was raised. Let's add another test to test_selected_item_from_list():

Code Block
languagepy
from sources.operations import select_item_from_list


def test_select_item_from_list():
    with pytest.raises(IndexError):
        select_item_from_list([1, 2, 3], 10)

    with pytest.raises(TypeError):
        select_item_from_list('list A', 'index')


Image RemovedImage Added

ValueError example

For ValueError example there is created new function called find_item_possition_in_list:

...

This function also doesn't have protection against fails that can occur during code execution. What if somebody provides an item and list which doesn't contain this element. There is no opportunity to find the position of the element which doesn't occur in the list. Let's prove it by test using the ValueError exception. 

Code Block
languagepy
from sources.operations import find_item_position_in_list


def test_find_item_position_in_list():
    with pytest.raises(ValueError):
        find_item_position_in_list([], 1)

...

As expected there is no change to find a position of element '1' in an empty list. Let's run the test and check the output. 

Image RemovedImage Added

The ValueError raised and test_find_item_position_in_list test passed.


 ValueError - advanced example

Firstly let's write another function called reshape_array.  The function arguments are a list (lst) and output array dimensions  (x and y). The result is an array containing the same data with a new shape. To transform list to array and reshape it there is the NumPy package needed. 

NumPy package installation:

Code Block
pip install numpy


If the NumPy package is installed we can import it and write the reshape_array function like in the snipped below:

Code Block
languagepy
import numpy as np


def reshape_array(lst, x, y):
    array = np.array(lst)
    return array.reshape((x, y))


It's impossible to reshape an array into any shape.  We can reshape 4 elements 1D array into 2 elements in 2 rows 2D array but we cannot reshape it into a 2 elements 3 rows 2D array as that would require 2x3 = 6 elements. We can check that using Pytest.

Code Block
languagepy
def test_reshape_array():
    with pytest.raises(ValueError):
        reshape_array([1, 2, 3, 4], 2, 3)

    assert (reshape_array([1, 2, 3, 4], 2, 2) == np.array([[1, 2], [3, 4]])).all()

We used np. ndarray. all() to check if two NumPy arrays are equivalent. 


Image Added

The first test passed and raised a ValueError exception because there is no possibility to reshape the 4 elements list into 2x3 array. The second test also passed - a list of 4 elements can be reshaped into 2x2 array. 


Info
The official documentation with listed available python built-in exceptions: https://docs.python.org/3/library/exceptions.html