ZurĂĽck Weiter 
Sie lernen, wie Sie Tests in Klassen, Modulen und Verzeichnissen organisieren. Ich werde Ihnen dann zeigen, wie Sie Markierungen verwenden, um zu markieren, welche Tests Sie ausführen möchten, und erläutern, wie integrierte Markierungen Ihnen helfen können, Tests zu überspringen und Tests zu markieren, wobei ein Fehler zu erwarten ist. Abschließend werde ich auf die Parametrisierung von Tests eingehen, mit der Tests mit unterschiedlichen Daten aufgerufen werden können.

Die Beispiele in diesem Buch wurden mit Python 3.6 und pytest 3.2 geschrieben. pytest 3.2 unterstĂĽtzt Python 2.6, 2.7 und Python 3.3+.
Der Quellcode für das Aufgabenprojekt sowie für alle in diesem Buch gezeigten Tests ist unter dem Link auf der Webseite des Buches unter pragprog.com verfügbar . Sie müssen den Quellcode nicht herunterladen, um den Testcode zu verstehen. Der Testcode wird in den Beispielen in einer praktischen Form dargestellt. Um jedoch die Aufgaben des Projekts zu verfolgen oder Testbeispiele anzupassen, um Ihr eigenes Projekt zu testen (Ihre Hände sind losgebunden!), Müssen Sie auf die Webseite des Buches gehen und die Arbeit herunterladen. Dort, auf der Webseite des Buches, gibt es einen Link für Errata- Nachrichten und ein Diskussionsforum .
Unter dem Spoiler befindet sich eine Liste der Artikel dieser Reihe.
Im vorherigen Kapitel haben Sie pytest ausgeführt. Sie haben gesehen, wie es mit Dateien und Verzeichnissen ausgeführt wird und wie viele der Optionen funktionieren. In diesem Kapitel erfahren Sie, wie Sie Testfunktionen im Kontext des Testens eines Python-Pakets schreiben. Wenn Sie pytest verwenden, um etwas anderes als ein Python-Paket zu testen, ist der größte Teil dieses Kapitels hilfreich.
Wir werden Tests fĂĽr das Aufgabenpaket schreiben. Bevor wir dies tun, werde ich ĂĽber die Struktur des Python-Distributionspakets und die Tests dafĂĽr sprechen sowie darĂĽber, wie die Tests das Testpaket anzeigen lassen. Dann werde ich Ihnen zeigen, wie Sie assert in Tests verwenden, wie Tests mit unvorhergesehenen Ausnahmen umgehen und erwartete Ausnahmen testen.
Am Ende werden wir viele Tests haben. Auf diese Weise lernen Sie, wie Sie Tests in Klassen, Modulen und Verzeichnissen organisieren. Ich werde Ihnen dann zeigen, wie Sie Markierungen verwenden, um zu markieren, welche Tests Sie ausführen möchten, und erläutern, wie integrierte Markierungen Ihnen helfen können, Tests zu überspringen und Tests zu markieren, wobei ein Fehler zu erwarten ist. Abschließend werde ich auf die Parametrisierung von Tests eingehen, mit der Tests mit unterschiedlichen Daten aufgerufen werden können.
Übersetzer Hinweis: Wenn Sie Python 3.5 oder 3.6 verwenden, erhalten Sie beim Ausführen der Tests in Kapitel 2 möglicherweise Nachrichten wie diese

Dieses Problem wird ...\code\tasks_proj\src\tasks\tasksdb_tinydb.py
indem ...\code\tasks_proj\src\tasks\tasksdb_tinydb.py
und das Aufgabenpaket neu installiert wird
$ cd /path/to/code $ pip install ./tasks_proj/`
Sie eids
doc_ids
benannten Parameter eids
fĂĽr doc_ids
und eid
fĂĽr doc_id
im Modul ...\code\tasks_proj\src\tasks\tasksdb_tinydb.py
Erklärungen Siehe #83783
hier.
Pakettests
Um zu lernen, wie Testfunktionen für das Python-Paket geschrieben werden, verwenden wir das Beispielprojekt Tasks, wie im Aufgabenprojekt auf Seite xii beschrieben. Tasks ist ein Python-Paket, das ein Befehlszeilentool mit demselben Aufgabennamen enthält.
Anhang 4, Packen und Verteilen von Python-Projekten auf Seite 175, enthält eine Erläuterung zum lokalen Verteilen Ihrer Projekte in einem kleinen Team oder global über PyPI. Ich werde daher nicht näher darauf eingehen. Schauen wir uns jedoch kurz an, was sich im Aufgabenprojekt befindet und wie verschiedene Dateien in den Testverlauf dieses Projekts passen.
Das Folgende ist die Dateistruktur des Aufgabenprojekts:
tasks_proj/ ├── CHANGELOG.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── setup.py ├── src │ └── tasks │ ├── __init__.py │ ├── api.py │ ├── cli.py │ ├── config.py │ ├── tasksdb_pymongo.py │ └── tasksdb_tinydb.py └── tests ├── conftest.py ├── pytest.ini ├── func │ ├── __init__.py │ ├── test_add.py │ └── ... └── unit ├── __init__.py ├── test_task.py └── ...
Ich habe eine vollständige Liste des Projekts (mit Ausnahme der vollständigen Liste der Testdateien) beigefügt, um anzugeben , wie die Tests in den Rest des Projekts passen, und auf mehrere Dateien zu verweisen, die für das Testen von entscheidender Bedeutung sind, nämlich conftest.py, pytest.ini , verschiedene __init__.py
Dateien und setup.py .
Alle Tests werden in Tests gespeichert und von den Paketquelldateien in src getrennt . Dies ist keine Pytest-Anforderung, aber eine bewährte Methode.
Alle Dateien der obersten Ebene, CHANGELOG.rst, LICENSE, README.rst, MANIFEST.in und setup.py , werden in Anhang 4, Packen und Verteilen von Python-Projekten, auf Seite 175 ausführlicher beschrieben. Setup.py ist jedoch wichtig für die Erstellung einer Distribution aus dem Paket, sowie für die Möglichkeit, das Paket lokal zu installieren, so dass das Paket für den Import verfügbar ist.
Funktions- und Komponententests sind in eigene Kataloge unterteilt. Dies ist eine willkürliche Entscheidung und nicht notwendig. Das Organisieren von Testdateien in mehreren Verzeichnissen erleichtert jedoch das Ausführen einer Teilmenge der Tests. Ich mag es, Funktions- und Komponententests zu trennen, da Funktionstests nur dann unterbrochen werden sollten, wenn wir die Funktionalität des Systems absichtlich ändern, während Komponententests während des Refactorings oder einer Änderung der Implementierung unterbrochen werden können.
Das Projekt enthält zwei Arten von __init__.py
Dateien: die im Verzeichnis src/
und die in tests/
. Die src/tasks/__init__.py
teilt Python mit, dass das Verzeichnis ein Paket ist. Es fungiert auch als Hauptschnittstelle fĂĽr das Paket, wenn jemand import tasks
. Es enthält Code zum Importieren bestimmter Funktionen aus api.py
, sodass cli.py
und unsere Testdateien auf tasks.add()
wie tasks.add()
zugreifen können, anstatt task.api.add ()
auszufĂĽhren. Die Dateien tests/func/__init__.py
und tests/unit/__init__.py
sind leer. Sie weisen pytest an, in einem Verzeichnis nach dem Stammverzeichnis des pytest.ini
und der Datei pytest.ini
.
Die Datei pytest.ini
ist optional. Es enthält die allgemeine Pytest-Konfiguration für das gesamte Projekt. Ihr Projekt sollte nicht mehr als eine davon haben. Es kann Anweisungen enthalten, die das Verhalten von pytest ändern, z. B. das Erstellen einer Liste von Parametern, die immer verwendet werden. Alles über pytest.ini
erfahren pytest.ini
in Kapitel 6, „Konfiguration“, auf Seite 113.
Die Datei conftest.py ist ebenfalls optional. Es wird als "lokales Plugin" angesehen und kann Hook-Funktionen und Fixtures enthalten. Hook-Funktionen sind eine Möglichkeit, Code in einen Teil der Pytest-Laufzeit einzubetten, um die Funktionsweise von Pytest zu ändern. Fixtures sind Setup- und Teardown-Funktionen, die vor und nach Testfunktionen ausgeführt werden und zur Darstellung der von den Tests verwendeten Ressourcen und Daten verwendet werden können. (Fixtures werden in Kapitel 3, Pytest Fixtures, auf Seite 49 und Kapitel 4, Builtin Fixtures, auf Seite 71 beschrieben, und Hook-Funktionen werden in Kapitel 5, „Plugins“, auf Seite 95 erläutert.) Die Hook-Funktionen und Fixtures, die in verwendet werden Tests in mehreren Unterverzeichnissen sollten in tests / conftest.py enthalten sein. Sie können mehrere conftest.py-Dateien haben. Sie können beispielsweise eine in den Tests und eine für jedes Test-Unterverzeichnis haben.
Wenn Sie dies noch nicht getan haben, können Sie eine Kopie des Quellcodes für dieses Projekt von der Website des Buches herunterladen . Alternativ können Sie an Ihrem Projekt mit einer ähnlichen Struktur arbeiten.
Hier ist test_task.py:
ch2 / task_proj / tests / unit / test_task.py
"""Test the Task data type.""" # -*- coding: utf-8 -*- from tasks import Task def test_asdict(): """_asdict() .""" t_task = Task('do something', 'okken', True, 21) t_dict = t_task._asdict() expected = {'summary': 'do something', 'owner': 'okken', 'done': True, 'id': 21} assert t_dict == expected def test_replace(): """replace () .""" t_before = Task('finish book', 'brian', False) t_after = t_before._replace(id=10, done=True) t_expected = Task('finish book', 'brian', True, 10) assert t_after == t_expected def test_defaults(): """ .""" t1 = Task() t2 = Task(None, None, False, None) assert t1 == t2 def test_member_access(): """ .field namedtuple.""" t = Task('buy milk', 'brian') assert t.summary == 'buy milk' assert t.owner == 'brian' assert (t.done, t.id) == (False, None)
Die Datei test_task.py enthält die folgende Importanweisung:
from tasks import Task
Der beste Weg, um Tests Aufgaben importieren oder etwas von Aufgaben importieren zu lassen, besteht darin, Aufgaben lokal mit pip zu installieren. Dies ist möglich, da es eine setup.py-Datei gibt, mit der pip direkt aufgerufen werden kann.
Installieren Sie Aufgaben, indem Sie pip install .
ausfĂĽhren pip install .
oder pip install -e .
aus dem Verzeichnis task_proj. Oder eine andere Option, um pip install -e tasks_proj
aus dem Verzeichnis eine Ebene höher pip install -e tasks_proj
:
$ cd /path/to/code $ pip install ./tasks_proj/ $ pip install --no-cache-dir ./tasks_proj/ Processing ./tasks_proj Collecting click (from tasks==0.1.0) Downloading click-6.7-py2.py3-none-any.whl (71kB) ... Collecting tinydb (from tasks==0.1.0) Downloading tinydb-3.4.0.tar.gz Collecting six (from tasks==0.1.0) Downloading six-1.10.0-py2.py3-none-any.whl Installing collected packages: click, tinydb, six, tasks Running setup.py install for tinydb ... done Running setup.py install for tasks ... done Successfully installed click-6.7 six-1.10.0 tasks-0.1.0 tinydb-3.4.0
Wenn Sie nur Tests für Aufgaben ausführen möchten, reicht dieser Befehl aus. Wenn Sie den Quellcode während der Installation von Aufgaben ändern möchten, müssen Sie die Installation mit der Option -e verwenden (für bearbeitbares "bearbeitbares"):
$ pip install -e ./tasks_proj/ Obtaining file:///path/to/code/tasks_proj Requirement already satisfied: click in /path/to/venv/lib/python3.6/site-packages (from tasks==0.1.0) Requirement already satisfied: tinydb in /path/to/venv/lib/python3.6/site-packages (from tasks==0.1.0) Requirement already satisfied: six in /path/to/venv/lib/python3.6/site-packages (from tasks==0.1.0) Installing collected packages: tasks Found existing installation: tasks 0.1.0 Uninstalling tasks-0.1.0: Successfully uninstalled tasks-0.1.0 Running setup.py develop for tasks Successfully installed tasks
Versuchen Sie nun, die Tests auszufĂĽhren:
$ cd /path/to/code/ch2/tasks_proj/tests/unit $ pytest test_task.py ===================== test session starts ====================== collected 4 items test_task.py .... =================== 4 passed in 0.01 seconds ===================
Import hat funktioniert! Andere Tests können jetzt sicher Importaufgaben verwenden. Jetzt schreiben wir einige Tests.
Assert-Anweisungen verwenden
Wenn Sie Testfunktionen schreiben, ist die reguläre Python-Anweisung assert Ihr primäres Werkzeug zum Melden von Testfehlern. Die Einfachheit bei pytest ist brillant. Dies ist es, was viele Entwickler dazu bringt, Pytest zusätzlich zu anderen Frameworks zu verwenden.
Wenn Sie eine andere Testplattform verwendet haben, haben Sie wahrscheinlich verschiedene Assert-Hilfsfunktionen gesehen. Im Folgenden finden Sie beispielsweise eine Liste einiger Formen von Assert- und Assert-Hilfsfunktionen:
Mit pytest können Sie assert <Ausdruck> für jeden Ausdruck verwenden. Wenn der Ausdruck bei der Konvertierung in bool als False ausgewertet wird, schlägt der Test fehl.
pytest enthält eine Funktion namens Assert Rewriting, die Assert-Aufrufe abfängt und durch etwas ersetzt, das Ihnen mehr darüber erzählen kann, warum Ihre Anweisungen fehlgeschlagen sind. Mal sehen, wie nützlich dieses Umschreiben ist, wenn wir uns einige Anweisungsfehler ansehen:
ch2 / task_proj / tests / unit / test_task_fail.py
""" the Task type .""" from tasks import Task def test_task_equality(): """ .""" t1 = Task('sit there', 'brian') t2 = Task('do something', 'okken') assert t1 == t2 def test_dict_equality(): """ , dicts, .""" t1_dict = Task('make sandwich', 'okken')._asdict() t2_dict = Task('make sandwich', 'okkem')._asdict() assert t1_dict == t2_dict
Alle diese Tests schlagen fehl, aber die Informationen in der Ablaufverfolgung sind interessant:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\unit>pytest test_task_fail.py ============================= test session starts ============================= collected 2 items test_task_fail.py FF ================================== FAILURES =================================== _____________________________ test_task_equality ______________________________ def test_task_equality(): """Different tasks should not be equal.""" t1 = Task('sit there', 'brian') t2 = Task('do something', 'okken') > assert t1 == t2 E AssertionError: assert Task(summary=...alse, id=None) == Task(summary='...alse, id=None) E At index 0 diff: 'sit there' != 'do something' E Use -v to get the full diff test_task_fail.py:9: AssertionError _____________________________ test_dict_equality ______________________________ def test_dict_equality(): """Different tasks compared as dicts should not be equal.""" t1_dict = Task('make sandwich', 'okken')._asdict() t2_dict = Task('make sandwich', 'okkem')._asdict() > assert t1_dict == t2_dict E AssertionError: assert OrderedDict([...('id', None)]) == OrderedDict([(...('id', None)]) E Omitting 3 identical items, use -vv to show E Differing items: E {'owner': 'okken'} != {'owner': 'okkem'} E Use -v to get the full diff test_task_fail.py:16: AssertionError ========================== 2 failed in 0.30 seconds ===========================
Wow! Das sind viele Informationen. Für jeden nicht erfolgreichen Test wird die genaue Fehlerzeichenfolge mit> einem Fehlerzeiger angezeigt. In den Zeilen E werden zusätzliche Informationen zum Assert-Fehler angezeigt, damit Sie besser verstehen, was schief gelaufen ist.
Ich habe absichtlich zwei Fehlpaarungen in test_task_equality()
, aber nur die erste wurde im vorherigen Code angezeigt. Versuchen wir es erneut mit dem Flag -v
, wie in der Fehlermeldung vorgeschlagen:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\unit>pytest -v test_task_fail.py ============================= test session starts ============================= collected 2 items test_task_fail.py::test_task_equality FAILED test_task_fail.py::test_dict_equality FAILED ================================== FAILURES =================================== _____________________________ test_task_equality ______________________________ def test_task_equality(): """Different tasks should not be equal.""" t1 = Task('sit there', 'brian') t2 = Task('do something', 'okken') > assert t1 == t2 E AssertionError: assert Task(summary=...alse, id=None) == Task(summary='...alse, id=None) E At index 0 diff: 'sit there' != 'do something' E Full diff: E - Task(summary='sit there', owner='brian', done=False, id=None) E ? ^^^ ^^^ ^^^^ E + Task(summary='do something', owner='okken', done=False, id=None) E ? +++ ^^^ ^^^ ^^^^ test_task_fail.py:9: AssertionError _____________________________ test_dict_equality ______________________________ def test_dict_equality(): """Different tasks compared as dicts should not be equal.""" t1_dict = Task('make sandwich', 'okken')._asdict() t2_dict = Task('make sandwich', 'okkem')._asdict() > assert t1_dict == t2_dict E AssertionError: assert OrderedDict([...('id', None)]) == OrderedDict([(...('id', None)]) E Omitting 3 identical items, use -vv to show E Differing items: E {'owner': 'okken'} != {'owner': 'okkem'} E Full diff: E {'summary': 'make sandwich', E - 'owner': 'okken', E ? ^... E E ...Full output truncated (5 lines hidden), use '-vv' to show test_task_fail.py:16: AssertionError ========================== 2 failed in 0.28 seconds ===========================
Nun, ich finde es verdammt cool! pytest konnte nicht nur beide Unterschiede feststellen, sondern zeigte uns auch genau, wo diese Unterschiede liegen. In diesem Beispiel wird nur die Gleichheitsbestätigung verwendet. Auf pytest.org finden Sie viele weitere Variationen der assert-Anweisung mit erstaunlichen Informationen zum Trace-Debugging.
Erwartete Ausnahme
Ausnahmen können an mehreren Stellen in der Aufgaben-API auftreten. Werfen wir einen kurzen Blick auf die Funktionen in task / api.py :
def add(task): # type: (Task) -\> int def get(task_id): # type: (int) -\> Task def list_tasks(owner=None): # type: (str|None) -\> list of Task def count(): # type: (None) -\> int def update(task_id, task): # type: (int, Task) -\> None def delete(task_id): # type: (int) -\> None def delete_all(): # type: () -\> None def unique_id(): # type: () -\> int def start_tasks_db(db_path, db_type): # type: (str, str) -\> None def stop_tasks_db(): # type: () -\> None
Zwischen dem CLI-Code in cli.py und dem API-Code in api.py besteht eine Vereinbarung darüber, welche Typen an die API-Funktionen übergeben werden. Bei API-Aufrufen erwarte ich, dass Ausnahmen ausgelöst werden, wenn der Typ falsch ist. Um sicherzustellen, dass diese Funktionen Ausnahmen auslösen, wenn sie nicht korrekt aufgerufen werden, verwenden Sie den falschen Typ in der Testfunktion, um absichtlich TypeError-Ausnahmen auszulösen und mit pytest.raises (erwartete Ausnahme) zu verwenden, zum Beispiel:
ch2 / task_proj / tests / func / test_api_exceptions.py
""" - API.""" import pytest import tasks def test_add_raises(): """add() param.""" with pytest.raises(TypeError): tasks.add(task='not a Task object')
In test_add_raises()
mit pytest.raises(TypeError)
: Die Anweisung gibt an, dass alles im nächsten Codeblock eine TypeError-Ausnahme auslösen soll. Wenn keine Ausnahme ausgelöst wird, schlägt der Test fehl. Wenn der Test eine weitere Ausnahme auslöst, schlägt er fehl.
Wir haben gerade die Art der Ausnahme in test_add_raises()
überprüft. Sie können auch die Ausschlussoptionen überprüfen. Für start_tasks_db(db_path, db_type)
sollte nicht nur db_type eine Zeichenfolge sein, sondern auch entweder 'tiny' oder 'mongo'. Sie können überprüfen, ob die Ausnahmemeldung korrekt ist, indem Sie excinfo hinzufügen:
ch2 / task_proj / tests / func / test_api_exceptions.py
def test_start_tasks_db_raises(): """, .""" with pytest.raises(ValueError) as excinfo: tasks.start_tasks_db('some/great/path', 'mysql') exception_msg = excinfo.value.args[0] assert exception_msg == "db_type must be a 'tiny' or 'mongo'"
Dies ermöglicht es uns, diese Ausnahme genauer zu betrachten. Der Variablenname nach as (in diesem Fall excinfo) wird mit Ausnahmeinformationen gefüllt und ist vom Typ ExceptionInfo.
In unserem Fall möchten wir sicherstellen, dass der erste (und einzige) Ausnahmeparameter mit der Zeichenfolge übereinstimmt.
Testfunktionen markieren
pytest bietet einen coolen Mechanismus, um Marker in Testfunktionen zu setzen. Ein Test kann mehr als einen Marker haben, und ein Marker kann sich in mehreren Tests befinden.
Marker machen für Sie Sinn, nachdem Sie sie in Aktion gesehen haben. Angenommen, wir möchten eine Teilmenge unserer Tests als schnellen "Rauchtest" ausführen, um eine Vorstellung davon zu erhalten, ob eine ernsthafte Lücke im System besteht. Konventionell handelt es sich bei Rauchtests nicht um umfassende, gründliche Testsuiten, sondern um eine ausgewählte Teilmenge, die Sie schnell ausführen können, um dem Entwickler ein anständiges Bild des Zustands aller Teile des Systems zu vermitteln.
Um Ihrem Aufgabenprojekt eine @mark.pytest.smoke
hinzuzufĂĽgen, mĂĽssen @mark.pytest.smoke
fĂĽr einige Tests @mark.pytest.smoke
hinzufĂĽgen. test_api_exceptions.py
wir es mehreren test_api_exceptions.py
Tests hinzu (beachten Sie, dass Rauch- und Get- Marker nicht in pytest integriert sind; ich habe sie mir gerade ausgedacht):
ch2 / task_proj / tests / func / test_api_exceptions.py
@pytest.mark.smoke def test_list_raises(): """list() param.""" with pytest.raises(TypeError): tasks.list_tasks(owner=123) @pytest.mark.get @pytest.mark.smoke def test_get_raises(): """get() param.""" with pytest.raises(TypeError): tasks.get(task_id='123')
Lassen Sie uns nun nur die Tests ausfĂĽhren, die mit -m marker_name
:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests>cd func (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v -m "smoke" test_api_exceptions.py ============================= test session starts ============================= collected 7 items test_api_exceptions.py::test_list_raises PASSED test_api_exceptions.py::test_get_raises PASSED ============================= 5 tests deselected ============================== =================== 2 passed, 5 deselected in 0.18 seconds ==================== (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func> (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v -m "get" test_api_exceptions.py ============================= test session starts ============================= collected 7 items test_api_exceptions.py::test_get_raises PASSED ============================= 6 tests deselected ============================== =================== 1 passed, 6 deselected in 0.13 seconds ====================
Denken Sie daran, dass -v
fĂĽr --verbose
und es uns ermöglicht, die Namen der ausgeführten Tests --verbose
. Mit -m 'Rauch' werden beide Tests mit der Bezeichnung @ pytest.mark.smoke ausgefĂĽhrt.
Mit -m
'get' wird ein Test mit der @pytest.mark.get
. Ziemlich einfach.
Alles wird zu Wundern und Wundern! Ein Ausdruck nach -m
kann and
or
not
um mehrere Marker not
kombinieren:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v -m "smoke and get" test_api_exceptions.py ============================= test session starts ============================= collected 7 items test_api_exceptions.py::test_get_raises PASSED ============================= 6 tests deselected ============================== =================== 1 passed, 6 deselected in 0.13 seconds ====================
Wir haben diesen Test nur mit smoke
und get
Marker. Wir können not
:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v -m "smoke and not get" test_api_exceptions.py ============================= test session starts ============================= collected 7 items test_api_exceptions.py::test_list_raises PASSED ============================= 6 tests deselected ============================== =================== 1 passed, 6 deselected in 0.13 seconds ====================
Durch HinzufĂĽgen von -m 'smoke and not get'
wurde ein Test ausgewählt, der mit @pytest.mark.smoke
aber nicht mit @pytest.mark.get
.
RauchtestfĂĽllung
FrĂĽhere Tests scheinen noch kein vernĂĽnftiger Satz von smoke test
. Wir haben die Datenbank tatsächlich nicht berührt und keine Aufgaben hinzugefügt. Natürlich müsste ein smoke test
dies tun.
FĂĽgen wir einige Tests hinzu, die das HinzufĂĽgen einer Aufgabe in Betracht ziehen, und verwenden Sie einen davon als Teil unserer Rauchtestsuite:
ch2 / task_proj / tests / func / test_add.py
""" API tasks.add ().""" import pytest import tasks from tasks import Task def test_add_returns_valid_id(): """tasks.add(valid task) .""" # GIVEN an initialized tasks db # WHEN a new task is added # THEN returned task_id is of type int new_task = Task('do something') task_id = tasks.add(new_task) assert isinstance(task_id, int) @pytest.mark.smoke def test_added_task_has_id_set(): """, task_id tasks.add().""" # GIVEN an initialized tasks db # AND a new task is added new_task = Task('sit in chair', owner='me', done=True) task_id = tasks.add(new_task) # WHEN task is retrieved task_from_db = tasks.get(task_id) # THEN task_id matches id field assert task_from_db.id == task_id
Beide Tests haben einen GIVEN-Kommentar zur initialisierten Aufgabendatenbank, aber es gibt keine initialisierte Datenbank im Test. Wir können ein Gerät definieren, um die Datenbank vor dem Test zu initialisieren und nach dem Test zu bereinigen:
ch2 / task_proj / tests / func / test_add.py
@pytest.fixture(autouse=True) def initialized_tasks_db(tmpdir): """Connect to db before testing, disconnect after.""" # Setup : start db tasks.start_tasks_db(str(tmpdir), 'tiny') yield # # Teardown : stop db tasks.stop_tasks_db()
Das in diesem Beispiel verwendete Gerät tmpdir ist ein integriertes Gerät. In Kapitel 4, Eingebaute Geräte, auf Seite 71 erfahren Sie alles über integrierte Geräte. In Kapitel 3, Pytest-Geräte, auf Seite 49 erfahren Sie, wie Sie Ihre eigenen Geräte schreiben und wie sie funktionieren, einschließlich des hier verwendeten Autouse-Parameters.
Die in unserem Test verwendete automatische Maus zeigt, dass alle Tests in dieser Datei Fixture verwenden. Der Code vor der yield
wird vor jedem Test ausgefĂĽhrt. Der Code nach yield
wird nach dem Test ausgeführt. Falls gewünscht, kann die Ausbeute Daten an den Test zurückgeben. All dies und noch viel mehr werden Sie in den folgenden Kapiteln betrachten, aber hier müssen wir die Datenbank irgendwie zum Testen konfigurieren, damit ich nicht länger warten kann und Ihnen dieses Gerät zeigen muss (Fixture natürlich!). (pytest unterstützt auch die altmodischen Setup- und Teardown-Funktionen, wie sie beispielsweise bei Unittest und Nose verwendet werden , ist jedoch nicht so interessant. Wenn Sie jedoch interessiert sind, werden sie in Anhang 5, xUnit Fixtures, auf Seite 183 beschrieben.)
Lassen Sie uns die Diskussion über die Geräte vorerst verschieben und zum Beginn des Projekts gehen und unsere Rauchtestsuite ausführen:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>cd .. (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests>cd .. (venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v -m "smoke" ============================= test session starts ============================= collected 56 items tests/func/test_add.py::test_added_task_has_id_set PASSED tests/func/test_api_exceptions.py::test_list_raises PASSED tests/func/test_api_exceptions.py::test_get_raises PASSED ============================= 53 tests deselected ============================= =================== 3 passed, 53 deselected in 0.49 seconds ===================
Es zeigt, dass markierte Tests aus verschiedenen Dateien zusammen ausgeführt werden können.
Tests ĂĽberspringen
Obwohl die unter Markierungsüberprüfungsmethoden auf Seite 31 beschriebenen Markierungen die Namen Ihrer Wahl waren, enthält pytest einige nützliche integrierte Markierungen: skipif
, xfail
und xfail
. In diesem Abschnitt werde ich ĂĽber skip
und skipif
sprechen und im nächsten -xfail
.
Mit den skipif
skip
und " skipif
können Sie Tests überspringen, die nicht durchgeführt werden müssen. tasks.unique_id()
wir zum Beispiel an, wir wĂĽssten nicht, wie tasks.unique_id()
funktionieren soll. Jeder Anruf sollte eine andere Nummer zurĂĽckgeben? , ?
-, (, initialized_tasks_db
; ):
ch2/tasks_proj/tests/func/ test_unique_id_1.py
"""Test tasks.unique_id().""" import pytest import tasks def test_unique_id(): """ unique_id () .""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() assert id_1 != id_2
:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest test_unique_id_1.py ============================= test session starts ============================= collected 1 item test_unique_id_1.py F ================================== FAILURES =================================== _______________________________ test_unique_id ________________________________ def test_unique_id(): """Calling unique_id() twice should return different numbers.""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() > assert id_1 != id_2 E assert 1 != 1 test_unique_id_1.py:11: AssertionError ========================== 1 failed in 0.30 seconds ===========================
Hm. , . API , , docstring """Return an integer that does not exist in the db.""", , DB . . , :
ch2/tasks_proj/tests/func/ test_unique_id_2.py
@pytest.mark.skip(reason='misunderstood the API') def test_unique_id_1(): """ unique_id () .""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() assert id_1 != id_2 def test_unique_id_2(): """unique_id() id.""" ids = [] ids.append(tasks.add(Task('one'))) ids.append(tasks.add(Task('two'))) ids.append(tasks.add(Task('three'))) # id uid = tasks.unique_id() # , assert uid not in ids
, , , @pytest..skip()
.
:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_unique_id_2.py ============================= test session starts ============================= collected 2 items test_unique_id_2.py::test_unique_id_1 SKIPPED test_unique_id_2.py::test_unique_id_2 PASSED ===================== 1 passed, 1 skipped in 0.19 seconds =====================
, - , , 0.2.0 . skipif:
ch2/tasks_proj/tests/func/ test_unique_id_3.py
@pytest.mark.skipif(tasks.__version__ < '0.2.0', reason='not supported until version 0.2.0') def test_unique_id_1(): """ unique_id () .""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() assert id_1 != id_2
, skipif()
, Python. , , . skip , skipif . skip , skipif . ( reason ) skip , skipif xfail . :
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest test_unique_id_3.py ============================= test session starts ============================= collected 2 items test_unique_id_3.py s. ===================== 1 passed, 1 skipped in 0.20 seconds =====================
s.
, (skipped), (passed). , - -v
:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_unique_id_3.py ============================= test session starts ============================= collected 2 items test_unique_id_3.py::test_unique_id_1 SKIPPED test_unique_id_3.py::test_unique_id_2 PASSED ===================== 1 passed, 1 skipped in 0.19 seconds =====================
. -rs
:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -rs test_unique_id_3.py ============================= test session starts ============================= collected 2 items test_unique_id_3.py s. =========================== short test summary info =========================== SKIP [1] func\test_unique_id_3.py:8: not supported until version 0.2.0 ===================== 1 passed, 1 skipped in 0.22 seconds =====================
-r chars
:
$ pytest --help ... -r chars show extra test summary info as specified by chars ( , ) (f)ailed, (E)error, (s)skipped, (x)failed, (X)passed, (p)passed, (P)passed with output, (a)all except pP. ...
, .
skip
skipif
, . xfail
pytest , , . unique_id ()
, xfail
:
ch2/tasks_proj/tests/func/ test_unique_id_4.py
@pytest.mark.xfail(tasks.__version__ < '0.2.0', reason='not supported until version 0.2.0') def test_unique_id_1(): """ unique_id() .""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() assert id_1 != id_2 @pytest.mark.xfail() def test_unique_id_is_a_duck(): """ xfail.""" uid = tasks.unique_id() assert uid == 'a duck' @pytest.mark.xfail() def test_unique_id_not_a_duck(): """ xpass.""" uid = tasks.unique_id() assert uid != 'a duck'
Running this shows:
, , xfail
. == vs.! =. .
:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest test_unique_id_4.py ============================= test session starts ============================= collected 4 items test_unique_id_4.py xxX. =============== 1 passed, 2 xfailed, 1 xpassed in 0.36 seconds ================
X XFAIL, « ( expected to fail )». X XPASS «, , ( expected to fail but passed. )».
--verbose
:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_unique_id_4.py ============================= test session starts ============================= collected 4 items test_unique_id_4.py::test_unique_id_1 xfail test_unique_id_4.py::test_unique_id_is_a_duck xfail test_unique_id_4.py::test_unique_id_not_a_duck XPASS test_unique_id_4.py::test_unique_id_2 PASSED =============== 1 passed, 2 xfailed, 1 xpassed in 0.36 seconds ================
pytest , , , xfail
, FAIL. pytest.ini :
[pytest] xfail_strict=true
pytest.ini 6, , . 113.
, ​​ . . , , . , . . .
A Single Directory
, pytest :
(venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest tests\func --tb=no ============================= test session starts ============================= collected 50 items tests\func\test_add.py .. tests\func\test_add_variety.py ................................ tests\func\test_api_exceptions.py ....... tests\func\test_unique_id_1.py F tests\func\test_unique_id_2.py s. tests\func\test_unique_id_3.py s. tests\func\test_unique_id_4.py xxX. ==== 1 failed, 44 passed, 2 skipped, 2 xfailed, 1 xpassed in 1.75 seconds =====
, -v
, .
(venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v tests\func --tb=no ============================= test session starts =============================
...
collected 50 items tests\func\test_add.py::test_add_returns_valid_id PASSED tests\func\test_add.py::test_added_task_has_id_set PASSED tests\func\test_add_variety.py::test_add_1 PASSED tests\func\test_add_variety.py::test_add_2[task0] PASSED tests\func\test_add_variety.py::test_add_2[task1] PASSED tests\func\test_add_variety.py::test_add_2[task2] PASSED tests\func\test_add_variety.py::test_add_2[task3] PASSED tests\func\test_add_variety.py::test_add_3[sleep-None-False] PASSED ... tests\func\test_unique_id_2.py::test_unique_id_1 SKIPPED tests\func\test_unique_id_2.py::test_unique_id_2 PASSED ... tests\func\test_unique_id_4.py::test_unique_id_1 xfail tests\func\test_unique_id_4.py::test_unique_id_is_a_duck xfail tests\func\test_unique_id_4.py::test_unique_id_not_a_duck XPASS tests\func\test_unique_id_4.py::test_unique_id_2 PASSED ==== 1 failed, 44 passed, 2 skipped, 2 xfailed, 1 xpassed in 2.05 seconds =====
, .
File/Module
, , pytest:
$ cd /path/to/code/ch2/tasks_proj $ pytest tests/func/test_add.py =========================== test session starts =========================== collected 2 items tests/func/test_add.py .. ======================== 2 passed in 0.05 seconds =========================
.
, ::
:
$ cd /path/to/code/ch2/tasks_proj $ pytest -v tests/func/test_add.py::test_add_returns_valid_id =========================== test session starts =========================== collected 3 items tests/func/test_add.py::test_add_returns_valid_id PASSED ======================== 1 passed in 0.02 seconds =========================
-v
, , .
Test Class
Here's an example:
— , .
Hier ist ein Beispiel:
ch2/tasks_proj/tests/func/ test_api_exceptions.py
class TestUpdate(): """ tasks.update().""" def test_bad_id(self): """non-int id excption.""" with pytest.raises(TypeError): tasks.update(task_id={'dict instead': 1}, task=tasks.Task()) def test_bad_task(self): """A non-Task task excption.""" with pytest.raises(TypeError): tasks.update(task_id=1, task='not a task')
, update()
, . , , ::
, :
(venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v tests/func/test_api_exceptions.py::TestUpdate ============================= test session starts ============================= collected 2 items tests\func\test_api_exceptions.py::TestUpdate::test_bad_id PASSED tests\func\test_api_exceptions.py::TestUpdate::test_bad_task PASSED ========================== 2 passed in 0.12 seconds ===========================
A Single Test Method of a Test Class
, — ::
:
$ cd /path/to/code/ch2/tasks_proj $ pytest -v tests/func/test_api_exceptions.py::TestUpdate::test_bad_id ===================== test session starts ====================== collected 1 item tests/func/test_api_exceptions.py::TestUpdate::test_bad_id PASSED =================== 1 passed in 0.03 seconds ===================
,
, , , , . , pytest -v
.
-k
, . and
, or
not
. , _raises
:
(venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v -k _raises ============================= test session starts ============================= collected 56 items tests/func/test_api_exceptions.py::test_add_raises PASSED tests/func/test_api_exceptions.py::test_list_raises PASSED tests/func/test_api_exceptions.py::test_get_raises PASSED tests/func/test_api_exceptions.py::test_delete_raises PASSED tests/func/test_api_exceptions.py::test_start_tasks_db_raises PASSED ============================= 51 tests deselected ============================= =================== 5 passed, 51 deselected in 0.54 seconds ===================
and
not
test_delete_raises()
:
(venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v -k "_raises and not delete" ============================= test session starts ============================= collected 56 items tests/func/test_api_exceptions.py::test_add_raises PASSED tests/func/test_api_exceptions.py::test_list_raises PASSED tests/func/test_api_exceptions.py::test_get_raises PASSED tests/func/test_api_exceptions.py::test_start_tasks_db_raises PASSED ============================= 52 tests deselected ============================= =================== 4 passed, 52 deselected in 0.44 seconds ===================
, , , -k
. , , .
[Parametrized Testing]:
, , . . - pytest, - .
, , add()
:
ch2/tasks_proj/tests/func/ test_add_variety.py
""" API tasks.add().""" import pytest import tasks from tasks import Task def test_add_1(): """tasks.get () id, add() works.""" task = Task('breathe', 'BRIAN', True) task_id = tasks.add(task) t_from_db = tasks.get(task_id) # , , assert equivalent(t_from_db, task) def equivalent(t1, t2): """ .""" # , id return ((t1.summary == t2.summary) and (t1.owner == t2.owner) and (t1.done == t2.done)) @pytest.fixture(autouse=True) def initialized_tasks_db(tmpdir): """ , .""" tasks.start_tasks_db(str(tmpdir), 'tiny') yield tasks.stop_tasks_db()
tasks id
None
. id
. ==
, , . equivalent()
, id
. autouse
, , . , :
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_1 ============================= test session starts ============================= collected 1 item test_add_variety.py::test_add_1 PASSED ========================== 1 passed in 0.69 seconds ===========================
. , . , ? . @pytest.mark.parametrize(argnames, argvalues)
, :
ch2/tasks_proj/tests/func/ test_add_variety.py
@pytest.mark.parametrize('task', [Task('sleep', done=True), Task('wake', 'brian'), Task('breathe', 'BRIAN', True), Task('exercise', 'BrIaN', False)]) def test_add_2(task): """ .""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task)
parametrize()
— — 'task', . — , Task. pytest :
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_2 ============================= test session starts ============================= collected 4 items test_add_variety.py::test_add_2[task0] PASSED test_add_variety.py::test_add_2[task1] PASSED test_add_variety.py::test_add_2[task2] PASSED test_add_variety.py::test_add_2[task3] PASSED ========================== 4 passed in 0.69 seconds ===========================
parametrize()
. , , :
ch2/tasks_proj/tests/func/ test_add_variety.py
@pytest.mark.parametrize('summary, owner, done', [('sleep', None, False), ('wake', 'brian', False), ('breathe', 'BRIAN', True), ('eat eggs', 'BrIaN', False), ]) def test_add_3(summary, owner, done): """ .""" task = Task(summary, owner, done) task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task)
, pytest, , :
(venv35) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_3 ============================= test session starts ============================= platform win32 -- Python 3.5.2, pytest-3.5.1, py-1.5.3, pluggy-0.6.0 -- cachedir: ..\.pytest_cache rootdir: ...\bopytest-code\code\ch2\tasks_proj\tests, inifile: pytest.ini collected 4 items test_add_variety.py::test_add_3[sleep-None-False] PASSED [ 25%] test_add_variety.py::test_add_3[wake-brian-False] PASSED [ 50%] test_add_variety.py::test_add_3[breathe-BRIAN-True] PASSED [ 75%] test_add_variety.py::test_add_3[eat eggs-BrIaN-False] PASSED [100%] ========================== 4 passed in 0.37 seconds ===========================
, , pytest, :
(venv35) c:\BOOK\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_3[sleep-None-False] ============================= test session starts ============================= test_add_variety.py::test_add_3[sleep-None-False] PASSED [100%] ========================== 1 passed in 0.22 seconds ===========================
, :
(venv35) c:\BOOK\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v "test_add_variety.py::test_add_3[eat eggs-BrIaN-False]" ============================= test session starts ============================= collected 1 item test_add_variety.py::test_add_3[eat eggs-BrIaN-False] PASSED [100%] ========================== 1 passed in 0.56 seconds ===========================
, :
ch2/tasks_proj/tests/func/ test_add_variety.py
tasks_to_try = (Task('sleep', done=True), Task('wake', 'brian'), Task('wake', 'brian'), Task('breathe', 'BRIAN', True), Task('exercise', 'BrIaN', False)) @pytest.mark.parametrize('task', tasks_to_try) def test_add_4(task): """ .""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task)
. :
(venv35) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_4 ============================= test session starts ============================= collected 5 items test_add_variety.py::test_add_4[task0] PASSED [ 20%] test_add_variety.py::test_add_4[task1] PASSED [ 40%] test_add_variety.py::test_add_4[task2] PASSED [ 60%] test_add_variety.py::test_add_4[task3] PASSED [ 80%] test_add_variety.py::test_add_4[task4] PASSED [100%] ========================== 5 passed in 0.34 seconds ===========================
, . , ids parametrize()
, . ids
, . , tasks_to_try
, :
ch2/tasks_proj/tests/func/ test_add_variety.py
task_ids = ['Task({},{},{})'.format(t.summary, t.owner, t.done) for t in tasks_to_try] @pytest.mark.parametrize('task', tasks_to_try, ids=task_ids) def test_add_5(task): """Demonstrate ids.""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task)
, :
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_5 ============================= test session starts ============================= collected 5 items test_add_variety.py::test_add_5[Task(sleep,None,True)] PASSED test_add_variety.py::test_add_5[Task(wake,brian,False)0] PASSED test_add_variety.py::test_add_5[Task(wake,brian,False)1] PASSED test_add_variety.py::test_add_5[Task(breathe,BRIAN,True)] PASSED test_add_variety.py::test_add_5[Task(exercise,BrIaN,False)] PASSED ========================== 5 passed in 0.45 seconds ===========================
:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v "test_add_variety.py::test_add_5[Task(exercise,BrIaN,False)]" ============================= test session starts ============================= collected 1 item test_add_variety.py::test_add_5[Task(exercise,BrIaN,False)] PASSED ========================== 1 passed in 0.21 seconds ===========================
; shell. parametrize()
. :
ch2/tasks_proj/tests/func/ test_add_variety.py
@pytest.mark.parametrize('task', tasks_to_try, ids=task_ids) class TestAdd(): """ .""" def test_equivalent(self, task): """ , .""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task) def test_valid_id(self, task): """ .""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert t_from_db.id == task_id
:
(venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::TestAdd ============================= test session starts ============================= collected 10 items test_add_variety.py::TestAdd::test_equivalent[Task(sleep,None,True)] PASSED test_add_variety.py::TestAdd::test_equivalent[Task(wake,brian,False)0] PASSED test_add_variety.py::TestAdd::test_equivalent[Task(wake,brian,False)1] PASSED test_add_variety.py::TestAdd::test_equivalent[Task(breathe,BRIAN,True)] PASSED test_add_variety.py::TestAdd::test_equivalent[Task(exercise,BrIaN,False)] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(sleep,None,True)] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(wake,brian,False)0] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(wake,brian,False)1] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(breathe,BRIAN,True)] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(exercise,BrIaN,False)] PASSED ========================== 10 passed in 1.16 seconds ==========================
, @pytest.mark.parametrize()
. pytest.param(<value\>, id="something")
:
:
(venv35) ...\bopytest-code\code\ch2\tasks_proj\tests\func $ pytest -v test_add_variety.py::test_add_6 ======================================== test session starts ========================================= collected 3 items test_add_variety.py::test_add_6[just summary] PASSED [ 33%] test_add_variety.py::test_add_6[summary\owner] PASSED [ 66%] test_add_variety.py::test_add_6[summary\owner\done] PASSED [100%] ================================ 3 passed, 6 warnings in 0.35 seconds ================================
, id
.
Ăśbungen
- ,
task_proj
, - , pip install /path/to/tasks_proj
. - .
- pytest .
- pytest ,
tasks_proj/tests/func
. pytest , . . , ? - xfail , pytest tests .
tasks.count()
, . API , , , .- ?
test_api_exceptions.py
. , . ( api.py
.)
Was weiter
pytest . , , , . initialized_tasks_db
. / .
Sie können auch gemeinsamen Code trennen, sodass mehrere Testfunktionen dieselbe Einstellung verwenden können. Im nächsten Kapitel tauchen Sie tief in die wunderbare Welt des Fixtures Pytest ein.
ZurĂĽck Weiter 