[Python] Visualisiere und identifiziere langsame Teile mit pytest

Wenn die Anzahl der Tests in einem Projekt zunimmt, wird die CI-Rotation langsamer und die Belastung steigt, wenn die Tests häufig lokal durchgeführt werden. Ich frage mich, ob es 10.000 Tests gibt, aber manchmal bekomme ich die Frage: "Oh, es gibt einige langsame Testfälle?" Noch problematischer ist es, wenn der Produktcode für das langsame Testen verantwortlich ist. Da das Test-Framework von Python pytest verwendet, ist es eine Erinnerung daran, dass im Fall von pytest unten nach einer langsamen Verarbeitung wie dieser gesucht wird.

Ich habe hauptsächlich die folgenden zwei Dinge getan.

① Geben Sie einen Testfall mit langer Verarbeitungszeit mit der Option "--durations" von pytest aus (2) Profil mit pytest-profiling, um langsame Verarbeitungspunkte zu identifizieren.

Testcode

Dies ist der in diesem Artikel verwendete Testcode. Eine Liste von Ganzzahlen von 1 bis 10000000 in der Reihenfolge wird durch 4 Muster erstellt und schließlich durch Assert verglichen. Das Lesen des Ruhezustands in ** setup () ** ist nur enthalten, um das Verständnis bei der Ausgabe der Testverarbeitungszeit zu erleichtern, und ist für die Verarbeitung bedeutungslos.

Den Quellcode finden Sie unter hier.

test_sample.py


import time
import pytest
import sys


COUNT = 10000000

@pytest.fixture(scope='module')
def expected():
  #Erstellen erwarteter Werte zum Vergleich mit assert
  return [i for i in range(1, COUNT)]

@pytest.fixture(scope='function', autouse=True)
def setup():
  #Hülse, die in Bezug auf die Verarbeitung bedeutungslos ist
  time.sleep(0.1)
  yield
  time.sleep(0.2)

def test_1_1(expected):
  actual = [i for i in range(1, COUNT)]
  assert expected == actual


def test_1_2(expected):
  actual = list({i for i in range(1, COUNT)})
  assert expected == actual


def test_1_3(expected):
  actual = []
  for i in range(1, COUNT):
    actual.append(i)
  assert expected == actual


def test_1_4(expected):
  actual = []
  for i in range(1, COUNT):
    # actual = actual + [i]Es braucht Zeit, um zu sterben
    actual += [i]
  assert expected == actual

Bestimmen Sie, welcher Test langsam ist

Wenn Sie bei der Ausführung von pytest die Option "--durations = N" hinzufügen, wird der langsamste Test + N vor und nach der Verarbeitung (Setup / Teardown) im Ausführungsergebnis angezeigt. Wenn "N = 0" gesetzt ist, werden alle Ergebnisse ausgegeben.

Das Ergebnis der Ausführung von pytest mit "--durations = 0" ist wie folgt. Standardmäßig werden Ergebnisse von 0,01 s oder weniger ausgeblendet, aber mit der Option "-vv" werden alle Ergebnisse angezeigt.

=========== slowest test durations =========== 
2.13s call     tests/test_sample.py::test_1_3
1.25s call     tests/test_sample.py::test_1_4
1.08s call     tests/test_sample.py::test_1_2
0.81s call     tests/test_sample.py::test_1_1
0.66s setup    tests/test_sample.py::test_1_1
0.20s teardown tests/test_sample.py::test_1_2
0.20s teardown tests/test_sample.py::test_1_4
0.20s teardown tests/test_sample.py::test_1_3
0.20s teardown tests/test_sample.py::test_1_1
0.10s setup    tests/test_sample.py::test_1_4
0.10s setup    tests/test_sample.py::test_1_2
0.10s setup    tests/test_sample.py::test_1_3
============= 4 passed in 7.40s =============

Die ersten vier sind die Ausführungsergebnisse von vier Testfällen. Es ist schnell in einfacher Einschlussnotation zu schreiben. Als nächstes dauert die Vorverarbeitung (Einrichtung) von ** test_1_1 () ** ungefähr 0,66 Sekunden. Dies scheint die Addition der Verarbeitungszeit von ** erwartet () ** + 0,1 Sekunden Schlaf in ** setup () ** zu sein. Darauf folgen vier Teardowns für jeden anderen Test- und Setup-Prozess als ** test_1_1 () **. Dies entspricht der in ** setup () ** beschriebenen Ruhezeit.

Bestimmen Sie, welcher Prozess im Test langsam ist

Nachdem Sie festgestellt haben, welche Tests langsam sind, müssen Sie feststellen, welche Prozesse im Test langsam sind, um die Geschwindigkeit zu verbessern. Laut Net Wisdom gibt es eine Möglichkeit, pytest und profiler gleichzeitig auszuführen. Es ist jedoch erforderlich, das Ausgabeergebnis des Profils separat unter Verwendung von ** pstats ** oder dergleichen zu analysieren. Als ich untersuchte, ob dieser Bereich leicht zu realisieren ist, kam ich zu pytest-profiling.

pytest-profiling

Sie können es mit pip installieren.

pip install pytest-profiling

Sie können das Profilergebnis auch als SVG ausgeben, aber Graphviz ist erforderlich. Installieren Sie es daher separat.

Es ist einfach zu bedienen, fügen Sie einfach die Option --profile-svg hinzu, wenn Sie pytest ausführen.

pytest tests/test_sample.py --profile-svg

Bei Ausführung mit der Option --profile-svg führt pytest-profiling die folgende Verarbeitung durch.

  1. Führen Sie pytest und cProfile aus
  2. Analysieren Sie die Profilergebnisse mit pstats
  3. Konvertieren Sie das Analyseergebnis mit gprof2dot in ein Diagramm (Punktdatei).
  4. Konvertieren Sie das Diagramm mit dem Punktbefehl (Graphviz) in ein SVG-Bild.

Wenn Sie "--profile" anstelle der Option "--profile-svg" hinzufügen, wird anscheinend der obige dritte Prozess ausgeführt.

Nach einer Reihe von Verarbeitungen wird ein Prof-Verzeichnis erstellt und eine Prof-Datei mit pstats-Analyseinformationen und ein SVG-Bild darunter erstellt. Die folgenden Dateien werden im prof-Verzeichnis erstellt.

prof
├ combined.prof
├ combined.svg
├ test_1_1.prof
├ test_1_2.prof
├ test_1_3.prof
└ test_1_4.prof

** test_1_1.prof ** bis ** test_1_4.prof ** sind Profilanalyseinformationen für jeden Testfall. Der Name des Tests wird dem Dateinamen zugewiesen, aber Japanisch usw. wird erstellt, indem er durch einen Unterstrich ersetzt wird. ** kombinierte.prof ** sind Informationen zur Profilanalyse für alle durchgeführten Tests. ** kombinierte.svg ** wird in ein Diagramm konvertiert und dann in ein SVG-Bild konvertiert, das wie das folgende Bild aussieht.

combined.png

Das Lesen des Diagramms wird in der README-Datei von gprof2dot erläutert. Jeder Knoten des Diagramms enthält die folgenden Informationen.

+------------------------------+
|        function name         |
| total time % ( self time % ) |
|         total calls          |
+------------------------------+

Darüber hinaus werden die folgenden Informationen an der Kante beschrieben, die die aufrufende Funktion und die aufgerufene Funktion verbindet (ausgedrückt als Eltern und Kind).

           total time %
              calls
parent --------------------> children

Wenn Sie vom höchsten übergeordneten Knoten in absteigender Reihenfolge der Gesamtzeit% folgen, können Sie den langsam verarbeitenden Teil anscheinend leicht identifizieren.

Herausforderung: Es funktioniert nicht gut mit Windows

Ich habe es zuerst unter Windows ausgeführt, aber die Prof-Datei wurde generiert, aber die SVG-Datei wurde nicht erstellt. Wenn Sie genau hinschauen, sieht es so aus, als ob Unterstütztes Betriebssystem Windows nicht enthält.

Da die Prof-Datei generiert wird, war es jedoch möglich, die Prof-Datei manuell in SVG zu konvertieren. Ich denke, gprof2dot wird installiert, wenn Sie pytest-profiling installieren.

gprof2dot -f pstats prof/combined.prof > prof/tmp
dot -Tsvg -o prof/combined.svg prof/tmp

Übrigens habe ich es über das Visual Studio Code-Terminal ausgeführt, aber beim Ausführen des Punktbefehls ist ein Fehler aufgetreten. Es scheint nicht zu funktionieren, wenn die Standard-Shell Powershell ist. Der Wechsel von "Select Default Shell" zu cmd hat gut funktioniert.

Schließlich

Wenn Sie Folgendes in die pytest-Konfigurationsdatei schreiben und im Stammverzeichnis des Projekts ablegen, wird die pytest-Profilerstellung ausgeführt, wenn pytest ausgeführt wird.

pytest.ini


[pytest]
testpaths = tests
python_files = test_*.py
addopts = -vv --durations=0 --profile-svg
pytest_plugins = ['pytest_profiling']

Wenn es jedoch viele Testfälle gibt, sind die Analyseinformationen kompliziert. Identifizieren Sie daher zuerst den langsamen Teil des Tests mit der Option "--durations" und führen Sie dann die Pytest-Profilerstellung nur für den entsprechenden Test durch, um den langsamen Teil des Prozesses zu identifizieren. Ich denke, es ist einfacher, es zu tun. Wenn Sie es in die Konfigurationsdatei schreiben, wird es jedes Mal ausgeführt.

Recommended Posts

[Python] Visualisiere und identifiziere langsame Teile mit pytest
Probieren Sie die DB-Operation mit Python aus und visualisieren Sie sie mit d3
Visualisieren Sie den Bereich der internen und externen Einfügungen mit Python
Analysieren und visualisieren Sie JSON (Webanwendung ⑤ mit Python + Flask)
Programmieren mit Python und Tkinter
Ver- und Entschlüsselung mit Python
Python und Hardware-Verwenden von RS232C mit Python-
Python mit Pyenv und Venv
Funktioniert mit Python und R.
Kommunizieren Sie mit FX-5204PS mit Python und PyUSB
Leuchtendes Leben mit Python und OpenCV
Roboter läuft mit Arduino und Python
Installieren Sie Python 2.7.9 und Python 3.4.x mit pip.
Neuronales Netzwerk mit OpenCV 3 und Python 3
AM-Modulation und Demodulation mit Python
Scraping mit Node, Ruby und Python
Scraping mit Python, Selen und Chromedriver
Kratzen mit Python und schöner Suppe
JSON-Codierung und -Decodierung mit Python
Hadoop-Einführung und MapReduce mit Python
[GUI in Python] PyQt5-Drag & Drop-
Visualisieren Sie Python-Paketabhängigkeiten mit graphviz
Lesen und Schreiben von NetCDF mit Python
Ich habe mit PyQt5 und Python3 gespielt
Lesen und Schreiben von CSV mit Python
Mehrfachintegration mit Python und Sympy
Koexistenz von Python2 und 3 mit CircleCI (1.0)
Sugoroku-Spiel und Zusatzspiel mit Python
FM-Modulation und Demodulation mit Python
Kommunizieren Sie mit gRPC zwischen Elixir und Python
Datenpipeline-Aufbau mit Python und Luigi
Berechnen Sie das Standardgewicht und zeigen Sie es mit Python an
Überwachen Sie Mojo-Ausfälle mit Python und Skype
Spielen Sie handschriftliche Zahlen mit Python Teil 2 (identifizieren)
FM-Modulation und Demodulation mit Python Part 3
[Automatisierung] Bearbeiten Sie Maus und Tastatur mit Python
Passwortlose Authentifizierung mit RDS und IAM (Python)
Python-Installation und Paketverwaltung mit pip
Verwenden von Python und MeCab mit Azure Databricks
POST verschieden mit Python und empfange mit Flask
Bilder mit Pupil, Python und OpenCV aufnehmen
Fraktal zum Erstellen und Spielen mit Python
Ein Memo mit Python2.7 und Python3 in CentOS
Verwenden Sie PIL oder Pillow mit Cygwin Python
Erstellen und entschlüsseln Sie Caesar-Code mit Python
CentOS 6.4, Python 2.7.3, Apache, mod_wsgi, Django
Lesen und Schreiben von JSON-Dateien mit Python
Umgang mit "Jahren und Monaten" in Python
Ich habe Numba mit Python3.5 installiert und verwendet
Tweet-Analyse mit Python, Mecab und CaboCha
Verknüpfung von Python und JavaScript mit dem Jupiter-Notizbuch
Verkehrsüberwachung mit Kibana, ElasticSearch und Python
FM-Modulation und Demodulation mit Python Part 2
Visualisieren Sie Punkt P, der mit Python funktioniert
Mit Ruby (Rails) verschlüsseln und mit Python entschlüsseln
Laden Sie einfach mp3 / mp4 mit Python und youtube-dl herunter!
Betreiben Sie Haushaltsgeräte mit Python und IRKit
Bereinigen Sie die Python-Umgebung mit Pythonz und virtualenv
Üben des Web-Scrapings mit Python und Selen