Konfiguration

Mit Konfigurationsdateien könnt ihr den Ablauf von pytest beeinflussen. Wenn ihr immer wieder bestimmte Optionen in euren Tests verwendet, wie --verbose oder --strict-markers, könnt ihr diese in einer Konfigurationsdatei ablegen und müsst sie nicht immer wieder eingeben. Zusätzlich zu den Konfigurationsdateien gibt es eine Handvoll anderer Dateien, die bei der Verwendung von pytest nützlich sind, um die Arbeit beim Schreiben und Ausführen von Tests zu erleichtern:

pytest.ini

Dies ist die wichtigste Konfigurationsdatei von pytest, mit der ihr das Standardverhalten von pytest ändern könnt. Sie legt auch das Stammverzeichnis von pytest fest, oder rootdir.

conftest.py

Diese Datei enthält Test-Fixtures und Hook-Funktionen. Sie kann in rootdir oder in einem beliebigen Unterverzeichnis existieren.

__init__.py

Wenn diese Datei in Test-Unterverzeichnissen abgelegt wird, ermöglicht sie die Verwendung identischer Testdateinamen in mehreren Testverzeichnissen.

Wenn ihr bereits eine tox.ini, pyproject.toml oder setup.cfg in eurem Projekt habt, können sie an die Stelle der pytest.ini-Datei treten: tox.ini wird von tox verwendet, pyproject.toml und setup.cfg werden für die Paketierung von Python-Projekten verwendet und können zum Speichern von Einstellungen für verschiedene Werkzeuge, einschließlich pytest, verwendet werden.

Ihr solltet eine Konfigurationsdatei haben, entweder pytest.ini, oder einem pytest-Abschnitt in tox.ini, pyproject.toml oder in setup.cfg.

Sie Konfigurationsdatei legt das oberste Verzeichnis fest, von dem aus pytest gestartet wird.

Schauen wir uns einige dieser Dateien im Zusammenhang mit einer Projektverzeichnisstruktur an:

 items
 ├── …
 ├── pytest.ini
 ├── src
 │   └── …
 └── tests
     ├── __init__.py
     ├── conftest.py
     └── test_….py

Im Falle des items-Projekts, das wir bisher zum Testen verwendet haben, gibt es auf der obersten Ebene eine pytest.ini-Datei und ein Verzeichnis tests. Wir werden uns auf diese Struktur beziehen, wenn wir im weiteren Verlauf dieses Abschnitts über die verschiedenen Dateien sprechen.

Speichern von Einstellungen und Optionen in pytest.ini

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

[pytest] kennzeichnet den Beginn des pytest-Abschnitts. Danach folgen die einzelnen Einstellungen. Bei Konfigurationseinstellungen, die mehr als einen Wert zulassen, können die Werte entweder in eine oder in mehrere Zeilen geschrieben werden in der Form EINSTELLUNG = WERT1 WERT2. Bei markers hingegen ist nur ein Marker pro Zeile erlaubt.

Dieses Beispiel ist eine einfache pytest.ini-Datei, die ich so, oder so ähnlich in fast allen meinen Projekten verwende. Gehen wir kurz die einzelnen Zeilen durch:

addopts =

erlaubt die Angabe der pytest-Optionen, die wir immer in diesem Projekt ausführen wollen.

--strict-markers

weist pytest an, bei jedem nicht registrierten Marker, der im Testcode auftaucht, einen Fehler statt einer Warnung auszugeben. Hierdurch können wir Tippfehler bei Marker-Namen vermeiden.

--strict-config

weist pytest an, wenn beim Parsen von Konfigurationsdateien Schwierigkeiten auftauchen, nicht nur eine Warnung sondern einen Fehler auszugeben. Damit vermeiden wir, dass Tippfehler in der Konfigurationsdatei unbemerkt bleiben.

-ra

weist pytest an, am Ende eines Testlaufs nicht nur zusätzliche Informationen zu Failures und Errors anzuzeigen sondern auch eine Testzusammenfassung.

-r

zeigt zusätzliche Informationen zur Testzusammenfassung an.

a

zeigt alle außer den bestanden Tests an. Dies fügt den Failures und Errors die Informationen skipped, xfailed oder xpassed hinzu.

testpaths = tests

teilt pytest mit, wo es nach Tests suchen soll, wenn ihr auf der Kommandozeile keinen Datei- oder Verzeichnisnamen angegeben habt. In unserem Fall sucht pytest im tests-Verzeichnis.

Auf den ersten Blick mag es überflüssig erscheinen, testpaths auf tests zu setzen, da pytest sowieso dort sucht, und wir keine test_-Dateien in unseren src- oder docs-Verzeichnissen haben. Allerdings kann die Angabe eines testpaths-Verzeichnisses ein wenig Startzeit sparen, besonders wenn unsere src- oder docs- oder andere Verzeichnisse recht groß sind.

markers =

wird verwendet, um Marker zu deklarieren, wie in Auswahl von Tests mit eigenen Markern beschrieben.

Siehe auch

In den Konfigurationsdateien könnt ihr viele weitere Konfigurationseinstellungen und Befehlszeilenoptionen angeben, die ihr euch mit dem Befehl pytest --help anzeigen lassen könnt.

Andere Konfigurationsdateien verwenden

Wenn ihr Tests für ein Projekt schreibt, das bereits eine pyproject.toml, tox.ini oder setup.cfg-Datei hat, könnt ihr pytest.ini verwenden, um eure pytest-Konfigurationseinstellungen zu speichern, oder ihr könnt eure Konfigurationseinstellungen in einer dieser alternativen Konfigurationsdateien speichern. Die Syntax der beiden Nicht-ini-Dateien unterscheidet sich ein wenig, daher werden wir uns beide Dateien genauer ansehen.

pyproject.toml

Die pyproject.toml-Datei war ursprünglich für die Paketierung von Python-Projekten gedacht; sie kann jedoch auch für die Definition von Projekteinstellungen verwendet werden.

Da TOML ein anderer Standard für Konfigurationsdateien ist als .ini-Dateien, ist das Format auch ein wenig anders:

[tool.pytest.ini_options]
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"
    ]

Anstelle von [pytest] beginnt der Abschnitt mit [tool.pytest.ini_options], die Werte müssen in Anführungszeichen gesetzt werden und Listen von Werten müssen Listen von Zeichenketten in eckigen Klammern sein.

setup.cfg

Das Dateiformat der setup.cfg entspricht einer .ini-Datei:

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

Der einzige Unterschied zwischen dieser und der pytest.ini ist die Angabe des Abschnitts [tool:pytest].

Warnung

Der Parser der .cfg-Datei unterscheidet sich jedoch vom Parser der .ini-Datei, und dieser Unterschied kann Probleme verursachen, die schwer aufzuspüren sind, s.a. pytest-Dokumentation.

rootdir festlegen

Noch bevor pytest nach auszuführenden Testdateien sucht, liest es die Konfigurationsdatei pytest.ini, tox.ini, pyproject.toml oder setup.cfg, die einen pytest-Abschnitt enthält:

  • wenn ihr ein Testverzeichnis angegeben habt, beginnt pytest dort zu suchen

  • wenn ihr mehrere Dateien oder Verzeichnisse angegeben habt, beginnt pytest mit dem übergeordneten Verzeichnis

  • wenn ihr keine Datei oder kein Verzeichnis angebt, beginnt pytest im aktuellen Verzeichnis.

Wenn pytest eine Konfigurationsdatei im Startverzeichnis findet, ist das die Wurzel und wenn nicht, geht pytest den Verzeichnisbaum hoch, bis es eine Konfigurationsdatei findet, die einen pytest-Abschnitt enthält. Sobald pytest eine Konfigurationsdatei gefunden hat, markiert es das Verzeichnis, in dem es sie gefunden hat, als rootdir. Dieses Wurzelverzeichnis ist auch die relative Wurzel der IDs. pytest sagt euch auch, wo es eine Konfigurationsdatei gefunden hat. Durch diese Regeln können wir Tests auf verschiedenen Ebenen durchführen und sicher sein, dass pytest die richtige Konfigurationsdatei findet:

 $ 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 für die gemeinsame Nutzung von lokalen Fixtures und Hook-Funktionen

Die conftest.py-Datei wird verwendet, um Fixtures und Hook-Funktionen zu speichern, s.a. Test-Fixtures und Plugins. Ihr könnt so viele conftest.py-Dateien in einem Projekt haben, wie ihr wollt. Alles, was in einer conftest.py-Datei definiert ist, gilt für Tests in diesem Verzeichnis und allen Unterverzeichnissen. Wenn ihr eine conftest.py-Datei auf der obersten Testebene habt, können die dort definierten Fixtures für alle Tests verwendet werden. Wenn es dann spezielle Fixtures gibt, die nur für ein Unterverzeichnis gelten, können diese in einer anderen conftest.py-Datei in diesem Unterverzeichnis definiert werden. Zum Beispiel könnten die CLI-Tests andere Fixtures benötigen als die API-Tests, und einige könnt ihr auch gemeinsam nutzen.

Tipp

Es ist jedoch eine gute Idee, nur eine einzige conftest.py-Datei zu halten, damit ihr die Fixture-Definitionen leicht finden können. Auch wenn wir mit pytest --fixtures -v immer herausfinden können, wo eine Fixture definiert ist, so ist es dennoch einfacher, wenn sie immer in der einen conftest.py-Datei definiert ist.

__init__.py um Kollision von Testdateinamen zu vermeiden

Die __init__.py-Datei erlaubt es, doppelte Testdateinamen zu haben. Wenn ihr __init__.py-Dateien in jedem Test-Unterverzeichnis habt, könnt ihr denselben Testdateinamen in mehreren Verzeichnissen verwenden, z.B.:

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

Nun können wir die add-Funktionalität sowohl über die API als auch über die CLI testen, wobei eine test_add.py in beiden Verzeichnissen liegt:

$ 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 ===============================

Die meisten meiner Projekte starten mit folgender Konfiguration:

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