Skip to content

Pytest

Installing pytest

To install pytest, simply run:

pip install pytest

Test files are located in the tests directory and have either the test_ prefix or _test suffix. Within the test file, all test functions should start with test_.

Example Run

The simplest type of test is an assertion. This is where we assert that something is true. To see this in action, lets create a new test file called test_simple.py. Paste in the following code and save the file:

Simple Test

1
2
3
4
5
6
# Content of test_simple.py
def func(x):
    return x + 1

def test_answer():
    assert func(3) == 4

Here we have a simple function func() which takes an integer and adds one to it. The test is the function below, test_answer(), which inputs the value three into func() and expects an output of four. When we start the test by running pytest in the same directory, we get the following output:

============================= test session starts ==============================
platform darwin -- Python 3.9.12, pytest-7.1.2, pluggy-1.0.0
rootdir: /Users/name/dir
collected 1 item

test_sample.py .                                                         [100%]

============================== 1 passed in 0.03s ===============================

You can see that the function works as intended and the test passes. Now if we change line 6 to falsely assert that it should equal 5, this is the output that we receive when a test fails.

============================= test session starts ==============================
platform darwin -- Python 3.9.12, pytest-7.1.2, pluggy-1.0.0
rootdir: /Users/name/dir
collected 1 item

test_sample.py F                                                         [100%]

=================================== FAILURES ===================================
_________________________________ test_answer __________________________________

    def test_answer():
>       assert func(3) == 5
E       assert 4 == 5
E        +  where 4 = func(3)

test_sample.py:6: AssertionError
=========================== short test summary info ============================
FAILED test_sample.py::test_answer - assert 4 == 5
============================== 1 failed in 0.10s ===============================

In this output, pytest tells exactly where the error occurred, and it shows in the summary that the test failed because we are trying to assert that four equals five.

Multiple Tests in a File

Using pytest, it is also possible to place multiple tests in a single file. May also want to group them in a class, as done below:

Grouping Tests in a Class

# content of test_class.py
class TestClass:
   def test_one(self):
        x = "this"
        assert "h" in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, "check")

Danger

Be aware though that each test has a unique instance of the class. Try to foresee what will happen in the example test below, then run the test.

# content of test_class_demo.py
class TestClassDemoInstance:
    value = 0

    def test_one(self):
        self.value = 1
        assert self.value == 1

    def test_two(self):
        assert self.value == 1

Test Fixtures

Test functions can request fixtures they require by declaring them as arguments. When pytest runs a test, it looks at the parameters in that test function, and searches for fixtures that have the same names as those parameters. Once pytest finds them, it runs those fixtures, captures what they returned (if anything), and passes those objects into the test function as arguments.

Essentially, the pytest fixture system gives the ability to define a generic setup step that can be reused over and over, just like a normal function would be used. Two different tests can request the same fixture and have pytest give each test their own result from that fixture.

This is extremely useful for making sure tests aren’t affected by each other. We can use this system to make sure each test gets its own fresh batch of data and is starting from a clean state so it can provide consistent, repeatable results.

Fixture Example

import pytest


class Fruit:
    def __init__(self, name):
        self.name = name
        self.cubed = False

    def cube(self):
        self.cubed = True


class FruitSalad:
    def __init__(self, *fruit_bowl):
        self.fruit = fruit_bowl
        self._cube_fruit()

    def _cube_fruit(self):
        for fruit in self.fruit:
            fruit.cube()


# Arrange
@pytest.fixture
def fruit_bowl():
    return [Fruit("apple"), Fruit("banana")]


def test_fruit_salad(fruit_bowl):
    # Act
    fruit_salad = FruitSalad(*fruit_bowl)

    # Assert
    assert all(fruit.cubed for fruit in fruit_salad.fruit)

Parameterising of Test Arguments

A built in decorator, pytest.mark.parameterize, enables the parameterisation of arguments for a test function. This allows a compact way of testing various argument values in a test.

Parameterisation Example

# content of test_expectation.py
import pytest


@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
    assert eval(test_input) == expected

Note that it is also possible to combine multiple parameterised arguments by stacking the parameterized decorators in order to work through multiple permutations. The example below will set the values of (x,y) in the following order: (0,2), (1,2), (0,3), and (1,3).

Stacking Parameterised Decorators

import pytest


@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
    pass