This post will deal with unit testing in Python using PyTest framework. If you want to know more about unit testing, or testing in general, please refer to this page.

Getting started with PyTest

Firstly, let us begin by installing PyTest. If you want to install PyTest in your virtual environment, please begin by switching to the environment.

To install with pip, use the following command:

pip install pytest

You can also install the code-coverage tool, which I will be explaining towards the end of the post.

pip install pytest-cov

Basic Usage

We write unit tests in function. That is, every test should be a function. All the test functions should begin with test_ or else, PyTest will not identify them as tests.

Let us see an example of a function that checks whether a number is odd. We create a file called is_odd.py and write the following:

You can write the test in the same file or a different file. For now, let’s write the test for it in the same file:

Through the test, we are trying to assert that when an odd number (5) is passed to the function is_odd(), we get the result True. If it returns True, the test passes otherwise, the test fails.

To run the test, use the following command:

pytest

PyTest automatically detects all the tests from all the files and runs them. If you specifically want to run tests from one file, you will need to mention the file name too.

pytest is_odd.py

You could also run all the tests in a directory by providing the path:

pytest /project/

Once you run the test, you should see something like this:

====== 1 passed in 0.01s ======

Fixtures in PyTest

A Fixture is a convenient way, in PyTest, to pass a pre-initialized object or a variable to a test function. You can think of it as dependency injection. Fixtures really are just functions, which run before a test function to which it is applied.

Generally, fixtures are used in PyTest to do the following:

  • To feed data to the test function
  • To perform some task before and/or after running the test function (setup and teardown)

An example

To create a fixture, the decorator @pytest.fixture() should be appended before a function definition. In the example above, input_for_test is a fixture which returns 3. We have then used the fixture in the test test_answer. When you want to use a fixture in any test function, you will have to pass it as an argument to the test function. The name of the argument and the fixture should be exactly the same.

Using Fixture for setup and teardown

Consider a scenario where you want to initialize a class that connects to database, use the connection and dispose the connection after use. And you want to do this for multiple tests. If you do this in all the test functions, the initialization and the disposal will be repeated for all the tests.

Instead of initializing SomeDBClass and disposing it in every test function, you can use fixture and get rid of the repetitive lines.

Parametrize

Sometimes, we like to test a function multiple times for different parameters. For this, we generally write multiple calls to a function and assert multiple times.

For example, to test whether a function is_odd() is working correctly:

Instead of writing multiple assert statements, we can do it more easily using the parametrize feature of PyTest.

The decorator pytest.mark.parametrize tells the test function to take the parameters (num and result) from the parameters provided in the decorator. The test then runs for all the parameters.

The output is as below:

Code Coverage

To view the code coverage of the tests:

pytest --cov=filename

Or, if you want to check the code coverage for all the tests in directory:

pytest --cov=<path_to_directory>

Let us check the code coverage for the file is_odd.py that we created earlier.

*****

If you have any questions or comments regarding the post (or something else), please feel free to reach out through the comments.