Configuration

You can use configuration files to change the way pytest runs. If you repeatedly use certain options in your tests, such as --verbose or --strict-markers, you can store them in a configuration file so that you don’t have to enter them again and again. In addition to the configuration files, there are a handful of other files that are helpful when using pytest to make writing and running tests easier:

pyproject.toml

This is the most important configuration file of pytest, with which you can change the default behaviour of pytest. It also defines the root directory of pytest, or rootdir.

conftest.py

This file contains Test fixtures and hook functions. It can exist in rootdir or in any subdirectory.

__init__.py

If this file is stored in test subdirectories, it enables the use of identical test file names in several test directories.

If you already have a tox.ini, pytest.ini, or setup.cfg in your project, they can take the place of the configuration in the pyproject.toml file: tox.ini is used by tox, setup.cfg is used for packaging Python projects and can be used to store settings for various tools, including pytest.

You should have a configuration file, either in pyproject.toml, pytest.ini, tox.ini, or setup.cfg.

The configuration file defines the top-level directory from which pytest is started.

Let’s take a look at some of these files in the context of a project directory structure:

items
├── …
├── pyproject.toml
├── src
│   └── …
└── tests
    ├── __init__.py
    ├── conftest.py
    └── test_….py

In the case of the items project that we have used for testing so far, there is a pyproject.toml file and a tests directory at the top level. We will refer to this structure when we talk about the various files in the rest of this section.

Saving settings and options in pyproject.toml

The pyproject.toml file was originally intended for packaging Python projects; however, it can also be used to define project settings.

[tool.pytest]
minversion = "9.0"
addopts = [
  "--strict-markers",
  "--strict-config",
  "-ra",
]
testpaths = [
  "tests",
]
markers = [
  "exception: Only run expected exceptions",
  "finish: Only run finish tests",
  "smoke: Small subset of all tests",
  "num_items: Number of items to be pre-filled for the items_db fixture",
]

[tool.pytest] marks the start of the pytest section. This is followed by the individual settings. For configuration settings that allow more than one value, the values can be written either in one or more lines in the form :samp:`EINSTELLUNG = ["WERT1", "WERT2"]`.

This example is a simple pytest configuration in the pyproject.toml file, which I use in almost all of my projects in this form or a similar one. Let’s briefly go through the individual lines:

minversion

specifies the minimum pytest version.

addopts

allows you to specify the pytest options that we always want to execute in this project.

--strict-markers

instructs pytest to issue an error instead of a warning for every unregistered marker that appears in the test code. This allows us to avoid typos in marker names.

--strict-config

instructs pytest to issue an error instead of a warning if difficulties arise when parsing configuration files. This prevents typing errors in the configuration file from going unnoticed.

-ra

instructs pytest to display not only additional information on failures and errors at the end of a test run, but also a test summary.

-r

displays additional information on the test summary.

a

displays all but the passed tests. This adds the information skipped, xfailed or xpassed to the failures and errors.

testpaths = ["tests",]

tells pytest where to look for tests if you have not specified a file or directory name on the command line. In our case, pytest searches in the tests directory.

At first glance, it may seem superfluous to set testpaths to tests, as pytest searches there anyway and we do not have any test_ files in our src or docs directories. However, specifying a testpaths directory can save a little startup time, especially if our src, docs or other directories are quite large.

markers = ["exception: Only run expected exceptions", …]

is used to declare markers, as described in Selection of tests with your own markers.

See also

You can specify many other configuration settings and command line options in the configuration files, which you can display using the pytest --help command.

Using other configuration files

If you are writing tests for a project that already has a pytest.ini, tox.ini, or setup.cfg file, you can also store your pytest configuration settings in one of these alternative configuration files. The syntax of the *.ini files differs slightly, so we will take a closer look at these files.

pytest.ini

The pytest.ini file was originally intended for configuring pytest.

As TOML is a different standard for configuration files than .ini files, the format is also slightly different:

[pytest]
addopts =
    --strict-markers
    --strict-config
    -ra
testpaths = tests
markers =
    smoke: Small subset of all tests
    exception: Only run expected exceptions

setup.cfg

The file format of the setup.cfg corresponds to an .ini file:

[tool:pytest]
addopts =
    --strict-markers
    --strict-config
    -ra
testpaths = tests
markers =
    smoke: Small subset of all tests
    exception: Only run expected exceptions

The only difference between this and pytest.ini is the specification of the [tool:pytest] section.

Warning

However, the parser of the .cfg file differs from the parser of the .ini file, and this difference can cause problems that are difficult to track down, see also pytest documentation.

Set rootdir

Before pytest searches for test files to execute, it reads the configuration file pytest.ini, tox.ini, pyproject.toml or setup.cfg, which contains a pytest section:

  • if you have specified a test directory, pytest will start searching there

  • if you have specified several files or directories, pytest starts with the parent directory

  • if you do not specify a file or directory, pytest starts in the current directory.

If pytest finds a configuration file in the start directory, this is the root and if not, pytest goes up the directory tree until it finds a configuration file that contains a pytest section. Once pytest has found a configuration file, it marks the directory in which it found it as rootdir. This root directory is also the relative root of the IDs. pytest also tells you where it has found a configuration file. Using these rules, we can run tests at different levels and be sure that pytest finds the correct configuration file:

$ cd items
$ pytest
============================= test session starts ==============================
...
rootdir: /Users/veit/cusy/prj/items
configfile: pyproject.toml
testpaths: tests
plugins: Faker-19.11.0
collected 39 items
...

conftest.py for sharing local fixtures and hook functions

The conftest.py file is used to store fixtures and hook functions, see also Test fixtures and Plugins. You can have as many conftest.py files in a project as you like. Everything that is defined in a conftest.py file applies to tests in this directory and all subdirectories. If you have a conftest.py file at the top test level, the fixtures defined there can be used for all tests. If there are special fixtures that only apply to a subdirectory, these can be defined in another conftest.py file in this subdirectory. For example, the CLI tests may require different fixtures than the API tests, and you can also share some of them.

Tip

However, it is a good idea to keep only one conftest.py file so that you can easily find the fixture definitions. Even though we can always find out where a fixture is defined with pytest --fixtures -v, it is still easier if it is always defined in the one conftest.py file.

__init__.py to avoid collision of test file names

The __init__.py file allows you to have duplicate test filenames. If you have __init__.py files in each test subdirectory, you can use the same test filename in multiple directories, for example:

items
├── …
├── pytest.ini
├── src
│   └── …
└── tests
    ├── api
    │   ├── __init__.py
    │   └── test_add.py
    ├── cli
    │   ├── __init__.py
    │   ├── conftest.py
    │   └── test_add.py
    └── conftest.py

Now we can test the add functionality both via the API and via the CLI, whereby a test_add.py is located in both directories:

$ pytest
============================= test session starts ==============================
...
rootdir: /Users/veit/cusy/prj/items
configfile: pyproject.toml
testpaths: tests
plugins: Faker-19.11.0
collected 6 items

tests/api/test_add.py ....                                               [ 66%]
tests/cli/test_add.py ..                                                 [100%]

============================== 6 passed in 0.03s ===============================

Most of my projects start with the following configuration:

addopts =
   --strict-markers
   --strict-config
   -ra