Pytest tips & tricks
Last modified on Thu 18 Jan 2024

What is pytest?

Pytest is a testing framework for Python. For more info, check the documentation.

Conftest

conftest.py is a file in which you can set up your test configuration. The file should primarily consist of fixtures which are automatically detected and executed.

You can have multiple conftest files in your project. For example, you can have one conftest in the project root where you want to set up logic for the entire project. This one might be useful for the overall configuration, like setting up driver(s). And then you can also add an additional conftest file into your tests directory. In tests/conftest.py you could have fixtures that are used only on tests. Note that all tests will use those fixtures.

You can even add another conftest in other test subdirectories. For example, directory test_a could have its own conftest tests/test_a/conftest.py.

The structure might look something like this:

ROOT
conftest.py
└── tests
    └── conftest.py
    └── test_a
        └── conftest.py
        └── test_file_a.py
    └── test_b
        └── test_file_b.py

Fixtures and Hooks

Fixtures are defined using the @pytest.fixture decorator. They ensure the setup and teardown of resources before and after tests, providing a clean and consistent testing environment. Hooks are used for customizing the behavior of the testing framework itself and they are triggered at specific points during the test execution process.

Few things to have in mind when working with Fixtures and Hooks:

How to use fixtures

Pytest fixtures are sometimes confusing. They cannot be called directly, as you would call a function. However, they can be called in various other ways.

Let's say we have a fixture named fixture_a.

@pytest.fixture
def fixture_a():
    ...

To call fixture_a on a class, we have to use the @pytest.mark.usefixtures marker.

@pytest.mark.usefixtures("fixture_a")
class SomeClass:
    ...

To call fixture_a on a function, you have to pass it as an argument.

def test_one(fixture_a):
    ...

Autouse

Let's say there is some code that you want to run before or after every test, but you do not want to call it every time. autouse option comes in handy. By setting the autouse parameter in a fixture to True, it is called automatically.

import pytest

@pytest.fixture(scope="function", autouse=True)
def fixture_a():
    """Executed automatically before every test."""
    # Some code


@pytest.fixture(scope="class", autouse=True)
def fixture_b(driver):
    """Executed automatically after every class."""
    yield driver
    # Some code

Check the docs on autouse for more details.

Soft asserts

If you need soft asserts in your tests, pytest-check is a very useful plugin for pytest. It is easy to use and understand.

Example:

# test_file.py
import pytest_check as check

def test_example():
    check.is_true(1 > 2)

    check.equal("abc", "def")

Importing custom plugins

On occasion you will have to, or want to, create a custom plugin. Or just extract some of the code from conftest. For pytest to discover the plugin, it has to be somehow specified in a conftest file.

For example, you decided to add custom html_report and slack plugins. One way to do it is like this:

# conftest.py
pytest_plugins = [
    "utilities.plugins.html_report.plugin",
    "utilities.plugins.slack.plugin",
]