Tests in Python: Alle wichtigen Ansätze, Vor- und Nachteile. Yandex-Bericht

Vor Ihnen liegt der Bericht von Maria Zelenova Zelma- ein Entwickler in Foodil. Eine Stunde lang erzählte Mascha, was Testprogramme sind, was Tests sind, warum sie geschrieben werden. Anhand einfacher Beispiele erfahren Sie mehr über Bibliotheken zum Testen von Python-Code (unittest, pytest, mock), wie sie funktionieren und welche Unterschiede zwischen ihnen bestehen.





- Guten Abend, mein Name ist Masha, ich arbeite in der Datenanalyse-Abteilung von Eddila und heute haben wir einen Vortrag über das Testen mit Ihnen.







Zunächst werden wir mit Ihnen besprechen, welche Arten von Tests es im Allgemeinen gibt, und ich werde versuchen, Sie davon zu überzeugen, warum Sie Tests schreiben müssen. Dann werden wir darüber sprechen, was wir in Python haben, um direkt mit Tests zu arbeiten, mit ihren Schreib- und Hilfsmodulen. Am Ende erzähle ich Ihnen ein wenig über CI - ein unvermeidlicher Teil des Lebens in einem großen Unternehmen.







Ich möchte mit einem Beispiel beginnen. Ich werde versuchen, mit sehr beängstigenden Beispielen zu erklären, warum es sich lohnt, Tests zu schreiben.



Hier ist die Schnittstelle des THERAC 25-Programms. So hieß das Gerät für die Strahlentherapie von Krebspatienten, und alles lief extrem schlecht. Erstens hatte es eine schlechte Schnittstelle. Wenn man ihn ansieht, kann man bereits verstehen, dass er nicht sehr gut ist: Es war für die Ärzte unpraktisch, in all diesen Zahlen zu fahren. Infolgedessen kopierten sie Daten aus einer früheren Patientenakte und versuchten, nur das zu bearbeiten, was bearbeitet werden musste.



Es ist klar, dass sie vergessen haben, die Hälfte von ihnen zu korrigieren, und sich geirrt haben. Infolgedessen wurden die Patienten falsch behandelt. Die Benutzeroberfläche ist auch einen Test wert, es gibt nie zu viele Tests.



Neben der schlechten Benutzeroberfläche gab es im Backend noch viele weitere Probleme. Ich habe zwei identifiziert, die mir am ungeheuerlichsten erschienen:



  • . , . , . , .
  • C . THERAC , — , . . , , , - , - — .


Es würde sich lohnen, Tests zu schreiben. Weil es fünf Todesfälle gab und unklar ist, wie viele Menschen unter zu vielen Drogen gelitten haben.







Es gibt ein weiteres Beispiel, bei dem Sie in einigen Situationen durch das Schreiben von Tests viel Geld sparen können. Dies ist der Mars Climate Orbiter - ein Gerät, das die Atmosphäre in der Marsatmosphäre messen sollte, um zu sehen, was dort mit dem Klima geschah.



Aber das Modul, das vor Ort war, gab Befehle im SI-System, im metrischen System. Und das Modul in der Mars-Umlaufbahn hielt es für ein britisches Maßsystem und interpretierte es falsch.



Infolgedessen trat das Modul im falschen Winkel in die Atmosphäre ein und kollabierte. 125 Millionen Dollar sind gerade in den Müll gegangen, obwohl es den Anschein hat, dass es möglich ist, die Situation bei Tests zu simulieren und dies zu vermeiden. Aber es hat nicht geklappt.



Jetzt werde ich über prosaischere Gründe sprechen, warum Sie Tests schreiben sollten. Lassen Sie uns über jeden Punkt einzeln sprechen:



  • Tests stellen sicher, dass der Code funktioniert und Sie ein wenig beruhigen. In den Fällen, für die Sie Tests geschrieben haben, können Sie sicher sein, dass der Code funktioniert - wenn Sie ihn natürlich gut geschrieben haben. Besser schlafen. Es ist sehr wichtig.
  • . . , , , . , . , .



    , - , — , - . , . , , . , , , git blame, , , , .
  • . , . , . , , . - - , - - . , , , . - .
  • , . ? , , , , : , . 500 -, . . .
  • : — . , , . , , .



    , , , . , , .
  • . — , . , , . , .



    : - . , , . , , , - , .


Jetzt möchte ich ein wenig über die Klassifikationen der Testarten sprechen. Da sind viele von denen. Ich werde nur einige erwähnen.



Der Testprozess ist in Black-Box-Tests, Weiß- und Grautests unterteilt.







Black-Box-Tests sind Prozesse, bei denen der Tester nichts darüber weiß, was sich darin befindet. Er tut wie ein gewöhnlicher Benutzer etwas, ohne die Implementierungsspezifikationen zu kennen.



White-Box-Tests bedeuten, dass der Tester Zugriff auf alle benötigten Informationen hat, einschließlich des Quellcodes. Wir sind in einer solchen Situation, wenn wir einen Test für unseren eigenen Code schreiben.



Gray-Box-Tests liegen dazwischen. In diesem Fall kennen Sie einige Implementierungsdetails, aber nicht das Ganze.



Der Testprozess kann auch in manuell, halbautomatisch und automatisch unterteilt werden. Manuelle Tests werden von einer Person durchgeführt. Nehmen wir an, er klickt im Browser auf Schaltflächen, klickt irgendwo hin und sieht, was kaputt ist oder nicht. Halbautomatisches Testen ist, wenn ein Tester Testskripte ausführt. Wir können sagen, dass wir uns in einer solchen Situation befinden, wenn wir unsere Tests lokal ausführen. Automatisierte Tests beinhalten keine menschliche Beteiligung: Tests sollten automatisch und nicht von Hand durchgeführt werden.



Tests können auch nach Detaillierungsgrad unterteilt werden. Hier werden sie üblicherweise in Einheits- und Integrationstests unterteilt. Es kann zu Unstimmigkeiten kommen. Es gibt Leute, die Autotests als Unit-Tests bezeichnen. Aber eine klassischere Unterteilung ist so etwas.



Unit-Tests überprüfen den Betrieb einzelner Komponenten des Systems, und Integrationstests überprüfen das Bündel einiger Module. Manchmal gibt es auch Systemtests, die den Betrieb des gesamten Systems als Ganzes überprüfen. Es scheint jedoch eher eine große Variante von Integrationstests zu sein.



Die Tests für unseren Code sind Einheits- und Integrationstests. Es gibt Leute, die glauben, dass nur Integrationstests geschrieben werden sollten. Ich bin keiner von denen, ich denke, dass alles in Maßen sein sollte, und sowohl Unit-Tests, wenn Sie eine Komponente testen, als auch Integrationstests, wenn Sie etwas Großes testen, sind nützlich.



Warum denke ich so? Weil Unit-Tests normalerweise schneller sind. Wenn Sie etwas optimieren müssen, werden Sie sehr verärgert sein, dass Sie auf die Schaltfläche "Test ausführen" geklickt haben und dann drei Minuten warten, bis die Datenbank gestartet ist, Migrationen durchgeführt werden und etwas anderes passiert. In solchen Fällen sind Unit-Tests hilfreich. Sie können schnell und bequem einzeln ausgeführt werden. Aber wenn Sie die Komponententests behoben haben, lassen Sie uns die Integrationstests korrigieren.



Integrationstests sind ebenfalls sehr wichtig. Ein großes Plus ist, dass es mehr um das System geht. Ein weiteres großes Plus: Sie sind widerstandsfähiger gegen Code-Refactoring. Wenn Sie mit größerer Wahrscheinlichkeit eine kleine Funktion neu schreiben, ist es unwahrscheinlich, dass Sie die gesamte Pipeline mit derselben Häufigkeit ändern.







Es gibt viel mehr verschiedene Klassifikationen. Ich werde schnell auf das eingehen, was ich hier geschrieben habe, aber ich werde nicht im Detail darauf eingehen, das sind Worte, die Sie woanders hören können.



Rauchtests sind Tests auf kritische Funktionalität, die allerersten und einfachsten Tests. Wenn sie brechen, müssen Sie nicht mehr testen, sondern müssen sie reparieren. Nehmen wir an, die Anwendung wurde gestartet, ist nicht abgestürzt - großartig, der Rauchtest hat bestanden.



Es gibt Regressionstests - Tests für alte Funktionen. Angenommen, Sie rollen eine neue Version und müssen überprüfen, ob in der alten Version nichts kaputt war. Dies ist die Aufgabe von Regressionstests.



Es gibt Kompatibilitätstests, Installationstests. Sie überprüfen, ob alles für Sie in verschiedenen Betriebssystemen und verschiedenen Betriebssystemversionen, in verschiedenen Browsern und verschiedenen Browserversionen korrekt funktioniert.



Abnahmetests sind Abnahmetests. Ich habe bereits darüber gesprochen, sie sprechen darüber, ob Ihre Änderung in die Produktion übernommen werden kann oder nicht.



Es gibt auch Alpha- und Betatests. Beide Konzepte beziehen sich eher auf das Produkt. Wenn Sie eine mehr oder weniger fertige Version einer Version haben, aber dort nicht alles repariert ist, können Sie diese normalerweise an bedingt externe Personen oder an externe Personen, Freiwillige, weitergeben, damit diese Fehler für Sie finden, sie melden und Sie eine sehr gute Version veröffentlichen können. Je weniger fertig die Alpha-Version ist, desto mehr fertig ist die Beta. Im Beta-Test sollte mittlerweile fast alles in Ordnung sein.



Dann gibt es Leistungs- und Stresstests, Belastungstests. Sie überprüfen beispielsweise, wie Ihre Anwendung mit dem Laden umgeht. Es gibt einen Code. Sie haben berechnet, wie viele Benutzer, welche Anfragen es haben wird, welche RPS, wie viele Anfragen pro Sekunde kommen werden. Wir haben diese Situation simuliert, gestartet, geschaut - es gilt, es gilt nicht. Wenn nicht, überlegen Sie, was als nächstes zu tun ist. Vielleicht gibt es verschiedene Lösungen, um den Code zu optimieren oder die Menge an Hardware zu erhöhen.



Stresstests sind ungefähr gleich, nur die Belastung ist höher als erwartet. Wenn die Leistungstests die erwartete Last ergeben, können Sie in den Stresstests die Last erhöhen, bis sie bricht.



Linters stehen hier separat. Ich werde Ihnen etwas später etwas über Linters erzählen. Dies sind Code-Formatierungstests, ein Styleguide. In Python haben wir das Glück, PEP8 zu haben, einen einfachen Styleguide, dem jeder folgen sollte. Und wenn Sie etwas schreiben, fällt es Ihnen normalerweise schwer, dem Code zu folgen. Angenommen, Sie haben vergessen, eine leere Zeile einzufügen, eine zusätzliche Zeile zu erstellen oder eine zu lange Zeile zu hinterlassen. Es stört, weil Sie sich daran gewöhnen, dass Ihr Code im gleichen Stil geschrieben ist. Linters ermöglichen das automatische Auffangen solcher Dinge.



Mit der Theorie alles, dann werde ich darüber sprechen, was in Python ist.







Hier ist eine Liste einiger Bibliotheken. Ich werde nicht auf alle eingehen, aber auf die meisten. Natürlich werden wir über Unittest und Pytest sprechen. Dies sind Bibliotheken, die direkt zum Schreiben von Tests verwendet werden. Mock ist eine Hilfsbibliothek zum Erstellen von Scheinobjekten. Wir werden auch über sie sprechen. doctest ist ein Modul zum Testen der Dokumentation, flake8 ist ein Linter, wir werden sie uns auch ansehen. Ich werde nicht über Pylama und Tox sprechen. Wenn Sie interessiert sind, können Sie selbst sehen. Pylama ist auch ein Linter, sogar ein Metallinter, es kombiniert mehrere Pakete, sehr praktisch und gut. Die Tox-Bibliothek wird benötigt, wenn Sie Ihren Code in verschiedenen Umgebungen testen müssen - beispielsweise mit verschiedenen Versionen von Python oder mit verschiedenen Versionen von Bibliotheken. Tox hilft in diesem Sinne sehr.



Aber bevor ich über verschiedene Bibliotheken spreche, werde ich mit der Banalität beginnen. Fühlen Sie sich frei, assert in Ihrem Code zu verwenden. Es ist keine Schande. Es hilft oft zu verstehen, was los ist.







Angenommen, es gibt eine Funktion, die Ordnungsstatistiken berechnet, in die zwei Asserts geschrieben sind. Assert sollte in einer Funktion geschrieben werden, wenn es sich um extremen Unsinn handelt, der nicht im Code enthalten sein sollte. Dies sind sehr extreme Fälle, wahrscheinlich werden Sie sie in der Produktion nicht einmal treffen. Das heißt, wenn Sie den Code durcheinander bringen, wird er höchstwahrscheinlich in Ihren Tests fehlschlagen.



Asserts helfen beim Prototyping, Sie haben noch keinen Produktionscode, Sie können Assert überall festhalten - in der aufgerufenen Funktion überall. Dies ist nicht gut für ernsthafte Projekte, aber im Prototyping-Stadium recht gut.



Angenommen, Sie möchten Assert aus irgendeinem Grund deaktivieren. Sie möchten beispielsweise, dass es in der Produktion niemals ausgelöst wird. Python hat hierfür eine spezielle Option.







Ich werde dir sagen, was doctest ist. Dies ist ein Modul, eine Python-Standardbibliothek zum Testen der Dokumentation. Warum ist es gut? In Code geschriebene Dokumentation wird häufig unterbrochen. Hier gibt es eine sehr kleine Spielzeugfunktion, man kann alles sehen. Wenn Sie jedoch einen großen Code und viele Parameter haben und am Ende etwas hinzugefügt haben, werden Sie mit sehr hoher Wahrscheinlichkeit vergessen, die Dokumentzeichenfolgen zu korrigieren. Doctest vermeidet diese Dinge. Sie reparieren etwas, aktualisieren hier nicht, führen doctest aus und es wird für Sie abstürzen. Sie erinnern sich also, was genau Sie nicht korrigiert haben, gehen und korrigieren.



Wie sieht es aus? Doctest sucht nach diesen Weihnachtsbäumen in Dokumentenstrings, führt sie dann aus und vergleicht, was erhalten wird.







Hier ist ein Beispiel für das Ausführen von doctest. Wir haben es gestartet, wir sehen, dass wir zwei Tests haben und einer von ihnen fiel - ganz auf den Fall. Großartig, wir haben einige gute klare Informationen über den Fehler gesehen.





Link von der Folie



Der Doctest enthält einige hilfreiche Anweisungen, die sich als nützlich erweisen könnten. Ich werde nicht über alle sprechen, aber einige, die mir am häufigsten erschienen, habe ich auf die Folie gelegt. Mit der SKIP-Direktive können Sie keinen Test für ein markiertes Beispiel ausführen. Die Anweisung IGNORE_EXCEPTION_DETAIL ignoriert den EXCEPTION-Test. Mit ELLIPSIS können Sie Auslassungspunkte anstelle einer beliebigen Stelle in der Ausgabe schreiben. FAIL_FAST stoppt nach dem ersten fehlgeschlagenen Test. Alles andere kann in der Dokumentation gelesen werden, es gibt viel. Ich zeige es Ihnen besser mit einem Beispiel.







Dieses Beispiel enthält eine ELLIPSIS-Direktive und eine IGNORE_EXCEPTION_DETAIL-Direktive. Sie sehen die K-te Ordnungsstatistik in der ELLIPSIS-Direktive, und wir erwarten, dass etwas kommt, das mit einer Neun beginnt und mit einer Neun endet. Es könnte alles in der Mitte sein. Ein solcher Test wird nicht fehlschlagen.



Unten finden Sie die Anweisung IGNORE_EXCEPTION_DETAIL. Sie überprüft nur, was im AssertionError enthalten ist. Sehen Sie, wir haben dort bla bla bla geschrieben. Der Test wird bestanden, es wird nicht bla bla bla mit der erwarteten iterierbaren als erstes Argument verglichen. AssertionError wird nur mit AssertionError verglichen. Dies sind nützliche Dinge, die Sie verwenden können.







Dann ist der Plan folgender: Ich werde Ihnen von Unittest erzählen, dann von Pytest. Ich werde sofort sagen, dass ich die Vorteile von unittest wahrscheinlich nicht kenne, außer dass es Teil der Standardbibliothek ist. Ich sehe keine Situation, die mich zwingen würde, jetzt unittest zu verwenden. Es gibt jedoch Projekte, die es verwenden. In jedem Fall ist es hilfreich zu wissen, wie die Syntax aussieht und wie sie ist.



Ein weiterer Punkt: Tests, die in unittest geschrieben wurden, wissen, wie man pytest sofort ausführt. Es ist ihm egal. (…)



Unittest sieht so aus. Es gibt eine Klasse, die mit dem Worttest beginnt. Im Inneren eine Funktion, die mit dem Worttest beginnt. Die Testklasse erbt von unittest.TestCase. Ich muss sofort sagen, dass ein Test hier richtig geschrieben ist und der andere Test falsch ist.



Der Top-Test, bei dem die normale Behauptung geschrieben wird, schlägt fehl, sieht aber seltsam aus. Lassen Sie uns einen Blick darauf werfen.







Befehl starten. Sie können unittest main in den Code selbst schreiben und von Python aus aufrufen.







Wir haben diesen Test ausgeführt und sehen, dass er einen AssertionError geschrieben hat, aber er hat nicht geschrieben, wo er hingefallen ist - im Gegensatz zum nächsten Test, bei dem self.assertEqual verwendet wurde. Hier steht klar geschrieben: Drei sind nicht gleich zwei.







Es muss natürlich repariert werden. Aber dann war diese magische Ausgabe auf dem Bildschirm nicht sichtbar.



Schauen wir uns das noch einmal an. Im ersten Fall haben wir assert geschrieben, im zweiten self.assertEqual. Leider ist dies der einzige Weg in unittest. Es gibt spezielle Funktionen - self.assertEqual, self.assertnotEqual und 100.500 weitere Funktionen, die Sie verwenden müssen, wenn Sie eine angemessene Fehlermeldung sehen möchten.



Warum passiert es? Denn assert ist eine Anweisung, die einen Bool und möglicherweise einen String empfängt, in diesem Fall jedoch Bool. Und er sieht, dass er wahr oder falsch ist, und er hat nirgendwo die linke und rechte Seite zu nehmen. Daher verfügt unittest über spezielle Funktionen, mit denen Fehlermeldungen korrekt angezeigt werden.



Dies ist meiner Meinung nach nicht sehr praktisch. Genauer gesagt ist es überhaupt nicht bequem, da dies einige spezielle Methoden sind, die nur in dieser Bibliothek enthalten sind. Sie unterscheiden sich von dem, was wir in der gewöhnlichen Sprache gewohnt sind.







Sie müssen sich nicht daran erinnern - wir werden später über Pytest sprechen, und ich hoffe, Sie werden meistens darin schreiben. Unittest verfügt über einen Zoo mit Funktionen, die Sie verwenden können, wenn Sie etwas testen und gute Fehlermeldungen erhalten möchten.



Lassen Sie uns als nächstes darüber sprechen, wie man Fixtures in unittest schreibt. Aber um das zu tun, muss ich Ihnen zuerst sagen, was Geräte sind. Dies sind Funktionen, die vor oder nach dem Ausführen des Tests aufgerufen werden. Sie werden benötigt, wenn der Test eine spezielle Einstellung vornehmen muss: Erstellen Sie nach dem Test eine temporäre Datei, löschen Sie die temporäre Datei. Datenbank erstellen, Datenbank löschen; Erstellen Sie eine Datenbank, schreiben Sie etwas darauf. Im Allgemeinen was auch immer. Mal sehen, wie es unittest aussieht.







Unittest verfügt über spezielle Methoden setUp und tearDown zum Schreiben eines Fixtures. Warum sie immer noch nicht nach PEP8 geschrieben sind, ist mir ein großes Rätsel. (...)



SetUp wird vor dem Test ausgeführt, TearDown wird nach dem Test ausgeführt. Es scheint mir, dass dies ein äußerst unbequemes Design ist. Warum? Denn erstens erhebt sich meine Hand nicht, um diese Namen zu schreiben: Ich lebe bereits in einer Welt, in der es noch PEP8 gibt. Zweitens haben Sie eine temporäre Datei, über die Sie nichts in den Argumenten des Tests selbst haben. Wo kommt er her? Es ist nicht sehr klar, warum es existiert und worum es geht.



Wenn wir eine kleine Klasse haben, die sich an den Bildschirm klammert, ist es cool, man kann es sehen. Und wenn Sie dieses riesige Blatt haben, werden Sie gefoltert, um zu suchen, was es war und warum er so ist, warum er sich so verhält.



Es gibt noch eine andere nicht so bequeme Funktion, bei der die Scheinwerfer nicht in Ordnung sind. Angenommen, wir haben eine Testklasse, die eine temporäre Datei benötigt, und eine andere Testklasse, die eine Datenbank benötigt. Ausgezeichnet. Sie haben eine Klasse geschrieben, setUp, tearDown erstellt und eine temporäre Datei erstellt / gelöscht. Wir haben eine andere Klasse geschrieben, darin haben wir auch setUp, tearDown geschrieben und eine Datenbank darin erstellt / gelöscht.



Frage. Es gibt eine dritte Gruppe von Tests, die beides benötigen. Was tun mit all dem? Ich sehe zwei Möglichkeiten. Oder nehmen Sie den Code und kopieren Sie ihn, aber es ist nicht sehr praktisch. Oder erstellen Sie eine neue Klasse, erben Sie sie von den beiden vorherigen und rufen Sie super auf. Im Allgemeinen funktioniert dies auch, sieht aber für Tests wie ein wilder Overkill aus.







Daher möchte ich, dass Ihre Vertrautheit mit Unittest auf theoretischer Ebene so bleibt. Als nächstes werden wir über eine bequemere Art sprechen, Tests zu schreiben, eine bequemere Bibliothek, das ist pytest.



Zuerst werde ich versuchen, Ihnen zu sagen, warum Pytest praktisch ist.





Link von der Folie



Erster Punkt: In pytest funktionieren Asserts normalerweise so, wie Sie es gewohnt sind, und sie geben normale Fehlerinformationen. Zweitens: Es gibt eine gute Dokumentation für pytest, bei der eine Reihe von Beispielen zerlegt werden und alles, was Sie wollen, alles, was Sie nicht verstehen, angezeigt werden kann.



Drittens sind Tests nur Funktionen, die mit test_ beginnen. Das heißt, Sie benötigen keine zusätzliche Klasse, schreiben einfach eine reguläre Funktion, nennen sie test_ und sie wird durch pytest ausgeführt. Dies ist praktisch, denn je einfacher es ist, Tests zu schreiben, desto wahrscheinlicher ist es, dass Sie den Test schreiben, anstatt ihn zu bewerten.



Pytest bietet eine Reihe nützlicher Funktionen. Sie können parametrisierte Tests schreiben. Es ist praktisch, Geräte mit verschiedenen Ebenen zu schreiben. Außerdem können Sie einige Feinheiten verwenden: xfail, Raises, Skip und einige andere. Es gibt viele Plugins in pytest, und Sie können Ihre eigenen schreiben.







Sehen wir uns ein Beispiel an. So sehen in pytest geschriebene Tests aus. Die Bedeutung ist die gleiche wie bei unittest, nur sieht sie viel prägnanter aus. Der erste Test besteht im Allgemeinen aus zwei Zeilen.







Führen Sie den Befehl python -m pytest aus. Ausgezeichnet. Zwei Tests bestanden, alles ist in Ordnung, wir können sehen, was sie bestanden haben und zu welcher Zeit.







Lassen Sie uns nun einen Test abbrechen und ihn so durchführen, dass wir Informationen über den Fehler haben. Drucken Sie Assert 3 == 2 und Fehler. Das heißt, wir sehen: Trotz der Tatsache, dass wir eine reguläre Zusicherung geschrieben haben, haben wir Informationen über den Fehler korrekt angezeigt, obwohl wir zuvor in unittest gesagt haben, dass assert einen Bool in einer Zeichenfolge oder einem Bool akzeptiert, so dass es problematisch ist, Informationen über den Fehler anzuzeigen.



Man mag sich fragen, warum das alles funktioniert? Weil sie im Pytest versucht haben, den hässlichen Teil für die Schnittstelle aufzuräumen. Pytest analysiert zuerst Ihren Code und er erscheint als eine Art Baumstruktur, ein abstrakter Syntaxbaum. In dieser Struktur haben Sie Operatoren an den Eckpunkten und Operanden an den Blättern. Assert ist ein Operator. Es steht oben auf dem Baum, und in diesem Moment, bevor Sie dem Dolmetscher alles geben, können Sie diese Behauptung durch eine interne Funktion ersetzen, die Selbstbeobachtung durchführt und versteht, was sich auf Ihrer linken und rechten Seite befindet. Tatsächlich wird dies bereits dem Interpreter zugeführt, wobei Assert ersetzt wird.



Ich werde nicht auf Details eingehen, es gibt einen LinkDarauf können Sie lesen, wie sie es gemacht haben. Aber ich liebe es, dass alles unter der Haube funktioniert. Der Benutzer sieht dies nicht. Er schreibt, wie er es gewohnt ist, dass die Bibliothek selbst den Rest erledigt. Sie müssen nicht einmal darüber nachdenken.



Weiter im Pytest für Standardtypen haben Sie sowieso gute Fehlerinformationen. Weil pytest weiß, wie diese Fehlerinformationen angezeigt werden. Sie können jedoch benutzerdefinierte Datentypen in Ihrem Test vergleichen, z. B. Bäume oder etwas Komplexes, und pytest weiß möglicherweise nicht, wie Fehlerinformationen für sie angezeigt werden. In solchen Fällen können Sie einen speziellen Hook hinzufügen - hier ist ein Abschnitt in der Dokumentation - und in diesen Hook schreiben, wie die Fehlerinformationen aussehen sollen. Alles ist sehr flexibel und bequem.







Mal sehen, wie Fixtures im Pytest aussehen. Wenn in unittest setUp und tearDown geschrieben werden müssen, rufen Sie hier die übliche Funktion auf, wie Sie möchten. Wir haben den Dekorateur pytest.fixture oben geschrieben - großartig, es ist ein Fixture.



Und hier ist nicht das einfachste Beispiel. Das Gerät kann einfach eine Rückgabe durchführen, etwas zurückgeben, es ist analog zu setUp. In diesem Fall wird eine Art TearDown ausgeführt, dh genau hier wird nach dem Ende des Tests close aufgerufen und die temporäre Datei wird gelöscht.



Es scheint bequem zu sein. Sie haben eine beliebige Funktion, mit der Sie benennen können, was Sie wollen. Sie übergeben es explizit an den Test. Gefüllte_File übergeben, Sie wissen, was es ist. Von Ihnen wird nichts Besonderes verlangt. Verwenden Sie es im Allgemeinen. Dies ist viel bequemer als unittest.







Ein bisschen mehr über Spielpaarungen. Im Pytest ist es sehr einfach, Vorrichtungen mit unterschiedlichen Bereichen zu erstellen. Standardmäßig wird das Gerät mit der Funktionsebene erstellt. Dies bedeutet, dass es für jeden Test aufgerufen wird, an den Sie es bestanden haben. Das heißt, wenn es Ausbeute oder so etwas wie TearDown gibt, geschieht dies auch nach jedem Test.



Sie können scope = 'module' deklarieren und dann wird das Fixture einmal pro Modul ausgeführt. Angenommen, Sie möchten eine Datenbank einmal erstellen und nicht alle Migrationen nach jedem Test löschen und rollen.



Auch in Fixtures ist es möglich, das Argument autouse = True anzugeben, und dann wird das Fixture aufgerufen, unabhängig davon, ob Sie danach gefragt haben oder nicht. Es scheint, dass diese Option niemals verwendet werden sollte oder sollte, aber sehr vorsichtig, da es sich um eine implizite Sache handelt. Das Implizite wird am besten vermieden.







Wir haben diesen Code ausgeführt - mal sehen, was passiert ist. Es gibt einen Test, der davon abhängt, ob das Gerät mich bei Bedarf einmal anruft. Rufen Sie mich jedes Mal an. Rufen Sie mich gleichzeitig an, wenn Sie bei Bedarf ein Gerät auf Modulebene verwenden. Wir sehen, dass das erste Mal, wenn wir Fixtures anrufen, mich bei Bedarf einmal anrufen, mich jedes Mal anrufen, was dies ausgibt, aber das Fixture mit autouse wurde auch aufgerufen, weil es egal ist, es wird immer aufgerufen.



Der zweite Test hängt von denselben Vorrichtungen ab. Wir sehen, dass das zweite Mal, wenn wir mich bei Bedarf einmal anrufen, nicht gedruckt wurde, da es sich auf Modulebene befindet, bereits einmal aufgerufen wurde und nicht erneut aufgerufen wird.



Darüber hinaus können Sie anhand dieses Beispiels erkennen, dass pytest keine derartigen Probleme aufweist, über die wir in unittest gesprochen haben, wenn Sie in einem Test möglicherweise eine Datenbank benötigen, in einem anderen - einer temporären Datei. Wie man sie normalerweise aggregiert, ist nicht klar. Hier ist die Antwort auf diese Frage in pytest. Wenn zwei Spiele bestanden wurden, befinden sich zwei Geräte im Inneren.



Ausgezeichnet, sehr komfortabel, kein Problem. Vorrichtungen sind sehr flexibel und können von anderen Vorrichtungen abhängen. Darin besteht kein Widerspruch, und pytest selbst nennt sie in der richtigen Reihenfolge.







Tatsächlich können Sie im Inneren Fixtures von anderen Fixtures erben, deren Umfang unterscheiden und ohne Autouse automatisch aktivieren. Er selbst wird sie in der richtigen Reihenfolge arrangieren und anrufen.



Hier haben wir den ersten Test, Test einen, der von rar_dependency_for_test_one abhängt, wobei dieses Gerät von einem anderen Gerät abhängt - und einem weiteren. Mal sehen, was im Auspuff passiert.







Wir haben gesehen, dass sie in der Reihenfolge ihrer Vererbung aufgerufen werden. Es gibt alle Geräte auf Funktionsebene, daher werden sie für jeden Test aufgerufen. Der zweite Test hängt von der seltenen Abhängigkeit ab, und die seltene Abhängigkeit hängt von der häufigen Abhängigkeit ab. Wir schauen uns den Auspuff an und sehen, dass vor dem Test zwei Vorrichtungen aufgerufen wurden.



Pytest hat eine spezielle Konfigurationsdatei conftest.py, in der Sie alle Fixtures ablegen können, und es ist gut, wenn Sie es platzieren: Wenn eine Person den Code einer anderen Person betrachtet, schaut sie sich normalerweise Fixtures im Conftest an.



Es ist nicht obligatorisch. Wenn es ein Gerät gibt, das Sie nur in dieser Datei benötigen, und Sie sicher wissen, dass es spezifisch und eng anwendbar ist und Sie es nicht in einer anderen Datei benötigen, können Sie es in der Datei deklarieren. Oder erstellen Sie eine Menge Conftest und sie werden alle auf verschiedenen Ebenen arbeiten.







Lassen Sie uns über die Funktionen sprechen, die in pytest sind. Wie gesagt, es ist sehr einfach, Tests zu parametrisieren. Hier sehen wir einen Test mit drei Parametersätzen: zwei Eingaben und eine, die erwartet wird. Wir übergeben sie an die Funktionsargumente und prüfen, ob das, was wir an die Eingabe übergeben haben, mit den Erwartungen übereinstimmt.







Mal sehen, wie es aussieht. Wir sehen, dass es hier drei Tests gibt. Das heißt, Pytest glaubt, dass dies drei Tests sind. Zwei gingen vorbei, einer fiel. Was ist hier gut? Für den Test, der gefallen ist, sehen wir die Argumente, wir sehen, auf welchen Parametersatz er gefallen ist.



Wenn Sie eine kleine Funktion haben und parametrisieren, sagt drei, können Sie mit Ihren Augen sehen, was genau gefallen ist. Wenn die Parameter jedoch viele Sätze enthalten, werden Sie sie nicht mit Ihren Augen sehen. Sie werden eher sehen, aber es wird sehr schwierig für Sie sein. Und es ist sehr praktisch, dass pytest alles auf diese Weise anzeigt - Sie können sofort sehen, in welchem ​​Fall der Test fehlgeschlagen ist.







Parametrisieren ist eine gute Sache. Und wenn Sie einmal einen Test geschrieben haben und dann viele, viele Parametersätze ausführen, ist dies eine gute Vorgehensweise. Machen Sie nicht viele Codevarianten für ähnliche Tests, sondern schreiben Sie einen Test einmal, dann machen Sie einen großen Satz von Parametern, und es wird funktionieren.



Es gibt viele weitere nützliche Dinge im Pytest. Wenn Sie darüber sprechen, reicht der Vortrag eindeutig nicht aus, so dass ich noch einmal nur einige zeigen werde. Der erste Test verwendet pytest.raises (), um zu zeigen, dass Sie eine Ausnahme erwarten. In diesem Fall besteht der Test, wenn ein AssertionError ausgelöst wird. Sie sollten eine Ausnahme auslösen lassen.



Das zweite praktische Ding ist xfail. Es ist ein Dekorateur, der den Test fehlschlagen lässt. Angenommen, Sie haben viele Tests, viel Code. Sie haben etwas überarbeitet, der Test schlug fehl. Gleichzeitig verstehen Sie, dass die Reparatur entweder nicht kritisch ist oder sehr teuer ist. Und du bist so: Okay, ich werde einen Dekorateur daran hängen, es wird grün, ich werde es später reparieren. Oder nehmen wir an, der Test begann zu fluten. Es ist klar, dass dies eine Vereinbarung mit dem eigenen Gewissen ist, aber manchmal ist es notwendig. Darüber hinaus ist xfail in dieser Form grün, unabhängig davon, ob der Test gefallen ist oder nicht. Sie können es trotzdem an den Parameter Strict = True übergeben. Dann ist es eine etwas andere Situation. Pytest wartet, bis der Test fehlschlägt. Wenn der Test erfolgreich ist, wird eine Fehlermeldung zurückgegeben und umgekehrt.



Eine andere nützliche Sache ist skipif. Es gibt nur einen Sprung, der keine Tests ausführt. Und da ist Skipif. Wenn Sie mit der Maus über diesen Dekorateur fahren, wird der Test unter bestimmten Bedingungen nicht ausgeführt.



In diesem Fall steht geschrieben, dass wenn ich eine Mac-Plattform habe, ich nicht starte, da der Test aus irgendeinem Grund fällt. Es passiert. Im Allgemeinen gibt es jedoch plattformspezifische Tests, die auf einer bestimmten Plattform immer fehlschlagen. Dann ist das nützlich.







Fangen wir an. Wir haben den Buchstaben X gesehen, wir haben S gesehen. X bezieht sich hier auf xfail, S - auf skipif. Das heißt, pytest zeigt, welchen Test wir komplett verpasst haben und welchen wir ausgeführt haben, aber wir sehen uns das Ergebnis nicht an.







Es gibt viele verschiedene nützliche Optionen im Pytest selbst. Ich kann sie hier natürlich nicht anzeigen, Sie können es in der Dokumentation sehen. Aber ich erzähle Ihnen von ein paar.



Hier ist eine nützliche Option --collect-only. Es wird eine Liste der gefundenen Tests angezeigt. Es gibt eine Option -k - Filterung nach Testnamen. Dies ist eine meiner Lieblingsoptionen: Wenn ein Test fehlschlägt, insbesondere wenn er komplex ist und Sie noch nicht wissen, wie Sie ihn beheben können, filtern Sie ihn und führen Sie ihn aus.



Sie möchten Zeit sparen und wahrscheinlich keinen Spaß daran haben, 15 andere Tests durchzuführen - Sie wissen, dass sie bestanden oder nicht bestanden wurden, aber Sie haben sie noch nicht erreicht. Führen Sie den abstürzenden Test aus, beheben Sie ihn und fahren Sie fort.



Es gibt auch eine sehr schöne Option -s, die die Ausgabe von stdout und stderr in Tests ermöglicht. Standardmäßig gibt pytest nur stdout und stderr für fehlgeschlagene Tests aus. Es gibt jedoch Zeiten, normalerweise in der Phase des Debuggens, in denen Sie etwas im Test ausgeben möchten und nicht wissen, ob der Test fehlschlägt. Es kann nicht fallen, aber Sie möchten im Test selbst sehen, was dort kommt und ausgegeben wird. Dann laufen Sie mit -s und Sie sollten sehen, was Sie wollen.



-v ist die Standardoption für Ausführlichkeit. Erhöhen Sie die Ausführlichkeit.



--lf, --last-failed ist eine Option, mit der Sie nur die Tests neu starten können, die im letzten Lauf fehlgeschlagen sind. --sw, --stepwise sind auch nützliche Funktionen wie -k. Wenn Sie die Tests nacheinander reparieren, führen Sie sie mit --stepwise aus, durchlaufen die grünen Tests und werden gestoppt, sobald der fehlgeschlagene Test angezeigt wird. Und wenn Sie --sw erneut ausführen, beginnt es mit diesem Test, der abgestürzt ist. Wenn es wieder fällt, hört es wieder auf. Wenn es nicht fällt, geht es bis zum nächsten Fall weiter.





Link von der Folie



In pytest gibt es eine Hauptkonfigurationsdatei pytest.ini. Darin können Sie das Standardverhalten von pytest ändern. Ich habe hier die Optionen angegeben, die sehr oft in der Konfigurationsdatei zu finden sind.



Testpfade sind die Pfade, auf denen pytest nach Tests sucht. addopts wird beim Start zur Befehlszeile hinzugefügt. Hier habe ich flake8 und Coverage Plugins zu Addopts hinzugefügt. Wir werden sie uns etwas später ansehen.





Link von der Folie



Es gibt viele verschiedene Plugins in pytest. Ich habe diejenigen geschrieben, die wieder überall verwendet werden. flake8 ist ein Linter, Coverage ist Code Coverage durch Tests. Dann gibt es eine ganze Reihe von Plugins, die das Arbeiten mit bestimmten Frameworks erleichtern: Pytest-Kolben, Pytest-Django, Pytest-Twisted, Pytest-Tornado. Es gibt wahrscheinlich noch etwas anderes.



Das xdist-Plugin wird verwendet, wenn Sie Tests parallel ausführen möchten. Mit dem Timeout-Plugin können Sie die Testlaufzeit begrenzen: Dies ist praktisch. Sie hängen einen Timeout-Dekorateur an den Test. Wenn der Test länger dauert, schlägt er fehl.







Lassen Sie uns einen Blick darauf werfen. Ich habe Coverage und flake8 zu pytest.ini hinzugefügt. Die Berichterstattung gab mir einen Bericht, ich habe dort eine Datei mit Tests, etwas davon wurde nicht aufgerufen, aber das ist in Ordnung :)



Hier ist die Datei k_stat.py, die bis zu fünf Anweisungen enthält. Dies entspricht ungefähr fünf Codezeilen. Die Abdeckung beträgt 100%, aber das liegt daran, dass meine Datei sehr klein ist.



Tatsächlich ist die Abdeckung normalerweise nicht hundertprozentig und sollte darüber hinaus nicht auf alle Fälle erreicht werden. Subjektiv scheint eine Testabdeckung von 60-70% für die Arbeit völlig ausreichend und normal zu sein.



Überschuss ist eine solche Metrik, dass selbst wenn man hundertprozentig ist, man nicht sagt, dass man großartig ist. Die Tatsache, dass Sie diesen Code aufgerufen haben, bedeutet nicht, dass Sie etwas überprüft haben. Sie können am Ende auch assert True schreiben. Sie müssen sich der Abdeckung angemessen nähern, für eine 100% ige Testabdeckung gibt es Fading und Roboter, aber die Leute müssen das nicht tun.



In pytest.ini habe ich noch ein Plugin angeschlossen. Hier sehen Sie --flake8, dies ist ein Linter, der meine Stilfehler zeigt, und einige andere, nicht von PEP8, sondern von Pyflakes.







Hier im Auspuff steht die Fehlernummer in PEP8 oder in Pyflakes. Im Allgemeinen ist alles klar. Die Zeile ist zu lang. Für die Neudefinition benötigen Sie zwei Leerzeilen. Am Ende der Datei benötigen Sie eine Leerzeile. Am Ende steht, dass CitizenImport nicht für mich verwendet wird. Im Allgemeinen können Sie mit Lintern grobe Fehler und Fehler im Code-Design abfangen.







Wir haben bereits über das Timeout-Plugin gesprochen, mit dem Sie die Testlaufzeit begrenzen können. Für einige Perftests ist die Laufzeit wichtig. Und Sie können es innerhalb von Tests mit time.time und timeit begrenzen. Oder verwenden Sie das Timeout-Plugin, was auch sehr praktisch ist. Wenn der Test zu viel funktioniert, kann er auf verschiedene Arten profiliert werden, z. B. cProfile, aber Yura wird in seinem Vortrag darüber sprechen .







Wenn Sie eine IDE verwenden und es sich lohnt, zusätzliche Tools zu verwenden, habe ich hier insbesondere PyCharm, dann sind Tests sehr einfach, direkt von dort aus auszuführen.







Es bleibt über Mock zu sprechen. Nehmen wir an, wir haben Modul A, wir möchten es testen und es gibt andere Module, die wir nicht testen möchten. Einer von ihnen geht ins Netzwerk, der andere in die Datenbank, und der dritte ist ein einfaches Modul, das uns in keiner Weise stört. In solchen Fällen hilft uns Mock. Wenn wir einen Integrationstest schreiben, werden wir höchstwahrscheinlich eine Testdatenbank aufrufen, einen Testclient schreiben, und das ist auch in Ordnung. Es ist nur ein Integrationstest.



Es gibt Zeiten, in denen wir ein Unittest machen wollen, wenn wir nur ein Stück testen wollen. Dann brauchen wir einen Schein.







Mock ist eine Sammlung von Objekten, mit denen das reale Objekt ersetzt werden kann. Bei jedem Aufruf von Methoden und Attributen wird auch mock zurückgegeben.







In diesem Beispiel haben wir ein einfaches Modul. Wir werden es verlassen und einige komplexere durch Schein ersetzen. Wir werden jetzt sehen, wie es funktioniert.







Es wird hier deutlich gezeigt. Wir haben es importiert, wir sagen, dass m ein Mock ist. Mock zurückgerufen. Sie sagten, dass m eine Methode f hat. Mock zurückgerufen. Sie sagten, dass m das is_alive-Attribut ist. Großartig, ein weiterer Mock ist zurück. Und wir sehen, dass m und f einmal aufgerufen werden. Das heißt, es ist ein so kniffliges Objekt, in das die getattr-Methode umgeschrieben wird.







Schauen wir uns ein klareres Beispiel an. Angenommen, es gibt einen AliveChecker. Er verwendet eine Art http_session, benötigt ein Ziel und verfügt über eine do_check-Funktion, die True oder false zurückgibt, je nachdem, ob er 200 erhalten hat oder nicht. Dies ist ein leicht künstliches Beispiel. Angenommen, Sie können in do_check komplexe Logik erstellen.



Nehmen wir an, wir möchten nichts über die Sitzung testen, wir möchten nichts über die get-Methode wissen. Wir wollen nur do_check testen. Großartig, lass es uns testen.







Sie können es so machen. Mock http_session, hier heißt es pseudo_client. Wir verspotten ihre get-Methode, wir sagen, get ist ein Mock, der 200 zurückgibt. Wir starten, erstellen daraus einen AliveChecker, starten ihn. Dieser Test wird funktionieren.



Überprüfen wir außerdem, ob get einmal und mit genau den gleichen Argumenten wie geschrieben aufgerufen wurde. Das heißt, wir haben do_check aufgerufen, ohne zu wissen, um welche Sitzung es sich handelt oder welche Methoden es gibt. Wir haben sie nur eingefroren. Das einzige, was wir wissen, ist, dass es 200 zurückgegeben hat.







Ein weiteres Beispiel. Es ist dem vorherigen sehr ähnlich. Das einzige, was hier ist, ist side_effect anstelle von return_value. Aber das macht der Mock. In diesem Fall wird eine Ausnahme ausgelöst. Die Assert-Zeile wurde geändert, um nicht AliveChecker.do_check () zu bestätigen. Das heißt, wir sehen, dass der Scheck nicht bestanden wird.



Dies sind zwei Beispiele, wie Sie die Funktion do_check testen können, ohne zu wissen, was von oben und was in diese Klasse gelangt ist.







Das Beispiel sieht natürlich künstlich aus: Es ist nicht ganz klar, warum 200 oder nicht 200 geprüft werden, es gibt nur ein Minimum an Logik. Aber stellen wir uns vor, wir machen je nach Rückkehrcode etwas Kniffliges. Und dann scheint ein solcher Test viel aussagekräftiger zu sein. Wir haben gesehen, dass 200 hereinkommen, und dann überprüfen wir die Verarbeitungslogik. Wenn nicht 200 - das gleiche.







Sie können Bibliotheken auch mit mock patchen. Angenommen, Sie haben bereits eine Bibliothek und müssen etwas daran ändern. Hier ist ein Beispiel, wir haben den Sinus gepatcht. Jetzt gibt er immer eine Zwei zurück. Ausgezeichnet.



Wir sehen auch, dass m zweimal aufgerufen wurde. Mock weiß natürlich nichts über die internen APIs der Methoden, die Sie verspotten, und ist im Allgemeinen nicht verpflichtet, diese abzugleichen. Mit mock können Sie jedoch überprüfen, wie Sie angerufen haben, wie oft und mit welchen Argumenten. In diesem Sinne hilft es, den Code zu testen.







Ich möchte Sie vor einem Fall warnen, in dem es ein Modul und einen riesigen Mock gibt. Bitte gehen Sie alles vernünftig an. Wenn Sie einfache Dinge haben, machen Sie sie nicht nass. Je mehr Mock Sie in Ihrem Test haben, desto mehr entfernen Sie sich von der Realität: Ihre API stimmt möglicherweise nicht überein, und im Allgemeinen ist dies nicht genau das, was Sie testen. Sie müssen nicht alles unnötig einweichen. Gehen Sie den Prozess intelligent an.







Wir haben den letzten kleinen Teil über die kontinuierliche Integration übrig. Wenn Sie ein Haustierprojekt alleine entwickeln, können Sie Tests lokal durchführen, und es ist in Ordnung, sie funktionieren.



Sobald das Projekt wächst und mehr als ein Entwickler darin ist, funktioniert es nicht mehr. Erstens führt die Hälfte keine Tests lokal aus. Zweitens werden sie auf ihren Versionen ausgeführt. Irgendwo wird es Konflikte geben, alles wird ständig zusammenbrechen.



Dafür gibt es Continuous Integration, eine Entwicklungspraxis, bei der Kandidaten schnell in den Mainstream eingebunden werden. Gleichzeitig müssen sie eine Art automatische Montage oder Autotests in einem speziellen System durchlaufen. Sie haben den Code im Repository, die Commits, die Sie in Ihrem Hauptprojektzweig zusammenführen möchten. Bei diesen Commits werden Tests in einem speziellen System bestanden. Wenn die Tests grün sind, wird entweder das Commit in sich selbst gegossen, oder Sie haben die Möglichkeit, es einzufüllen.



Ein solches Schema hat natürlich ebenso wie alles seine Nachteile. Zumindest benötigen Sie zusätzliche Hardware - nicht die Tatsache, dass CI kostenlos ist. Aber in einem mehr oder weniger großen Unternehmen und auch in keinem großen Unternehmen kann man ohne CI nirgendwo hingehen.







Als Beispiel - ein Screenshot von TeamCity, einem der CIs. Es gibt eine Montage, die erfolgreich abgeschlossen wurde. Es gab viele Änderungen daran, es wurde auf so und so einem Agenten zu diesem und jenem Zeitpunkt gestartet. Dies ist ein Beispiel dafür, wann alles gut ist und infundiert werden kann.



Es gibt viele verschiedene CI-Systeme. Ich habe eine Liste geschrieben, wenn Sie interessiert sind, schauen Sie mal rein: AppVeyor, Jenkins, Travis, CircleCI, GoCD, Buildbot. Danke.






Weitere Vorträge des Videokurses über Python finden Sie in einem Beitrag über Habré .



All Articles