Pea Flower Under Cat 2-Python Erweiterte Quellcode-Analyse: Wie kann eine Klassenmethode in mehrere Methoden umgewandelt werden?

Pythons erweiterte Quellcode-Analyse: Wie kann eine Klassenmethode in mehrere Methoden umgewandelt werden?

Vorheriger Artikel " Wie implementiere ich parametrisierte Tests in Python?" In "erwähnte ich mehrere Bibliotheken, die parametrisierte Tests in Python implementieren, und hinterließ eine Frage:

Wie verwandeln sie eine Methode in mehrere Methoden und binden jede Methode mit den entsprechenden Parametern?

Lassen Sie es uns noch einmal destillieren. Die ursprüngliche Frage lautet: Wie kann man in einer Klasse einen Dekorator verwenden, um eine Klassenmethode in mehrere Klassenmethoden umzuwandeln (oder ähnliche Effekte zu erzielen)?

# 带有一个方法的测试类
class TestClass:
    def test_func(self):
        pass

# 使用装饰器,生成多个类方法
class TestClass:
    def test_func1(self):
        pass
    def test_func2(self):
        pass
    def test_func3(self):
        pass

Die Essenz von Dekorateuren in Python besteht darin, von einer zur anderen zu wechseln und die dekorierte Methode durch eine neue Methode zu ersetzen. Welche Mittel / Geheimwaffe haben die verschiedenen von uns eingeführten Bibliotheken bei der Implementierung der Parametrisierung verwendet?

1. Wie erreicht ddt die Parametrisierung?

Überprüfen Sie zunächst die Schreibmethode der ddt-Bibliothek im vorherigen Artikel:

import unittest
from ddt import ddt,data,unpack
@ddt
class MyTest(unittest.TestCase):
    @data((3, 1), (-1, 0), (1.2, 1.0))
    @unpack
    def test(self, first, second):
        pass

ddt kann 4 Dekoratoren bereitstellen: 1 @ddt zur Klasse hinzugefügt und 3 @data, @unpack und @file_data zur Klassenmethode hinzugefügt (oben nicht erwähnt).

Schauen Sie sich zunächst die Rolle der drei Dekoratoren an, die der Klassenmethode hinzugefügt wurden:

# ddt 版本(win):1.2.1
def data(*values):
    global index_len
    index_len = len(str(len(values)))
    return idata(values)

def idata(iterable):
    def wrapper(func):
        setattr(func, DATA_ATTR, iterable)
        return func
    return wrapper

def unpack(func):
    setattr(func, UNPACK_ATTR, True)
    return func

def file_data(value):
    def wrapper(func):
        setattr(func, FILE_ATTR, value)
        return func
    return wrapper

Ihre gemeinsame Aufgabe besteht darin, setattr () Attribute für Klassenmethoden hinzuzufügen. Wann werden diese Attribute verwendet? Werfen wir einen Blick auf den Quellcode von @ddt decorator, der der Klasse hinzugefügt wurde:

Die erste Schicht der for-Schleife durchläuft alle Klassenmethoden und dann zwei Zweige von if / elif, die DATA_ATTR / FILE_ATTR entsprechen, dh zwei Quellen entsprechender Parameter: data (@data) und file (@file_data).

Der elif-Zweig hat die Logik, die Datei zu analysieren, und ähnelt dann der Datenverarbeitung. Daher überspringen wir ihn und betrachten hauptsächlich den vorherigen if-Zweig. Die Logik dieses Teils ist sehr klar, die Hauptaufgaben sind wie folgt:

  • Parameterschlüsselwertpaare von Traversal-Klassenmethoden
  • Erstellen Sie einen neuen Methodennamen basierend auf dem ursprünglichen Paar aus Methode und Parameter
  • Ruft die Dokumentzeichenfolge der ursprünglichen Methode ab
  • Entpacken Sie die Parameter der Tupel- und Listentypen
  • Fügen Sie der Testklasse eine neue Testmethode hinzu und binden Sie Parameter und Dokumentzeichenfolgen

Wenn wir den Quellcode analysieren, können wir sehen, dass die drei Dekoratoren @data, @unpack und @file_data hauptsächlich Attribute festlegen und Parameter übergeben, und der @ddt-Dekorator ist die Kernverarbeitungslogik.

Dieses Schema, Dekoratoren (getrennt zu Klassen und Klassenmethoden hinzugefügt) zu verteilen und dann zu kombinieren, ist nicht elegant. Warum können sie nicht zusammen verwendet werden? Später werden wir seine verborgene Bedeutung analysieren. Drücken Sie zuerst auf die Tabelle, um zu sehen, wie andere Implementierungen aussehen.

2. Wie realisiere ich die Parametrisierung?

Überprüfen Sie zunächst das Schreiben der parametrisierten Bibliothek im vorherigen Artikel:

import unittest
from parameterized import parameterized
class MyTest(unittest.TestCase):
    @parameterized.expand([(3,1), (-1,0), (1.5,1.0)])
    def test_values(self, first, second):
        self.assertTrue(first > second)

Es bietet eine Decorator-Klasse @parameterized. Der Quellcode lautet wie folgt (Version 0.7.1). Hauptsächlich, um eine erste Überprüfung und Parameteranalyse durchzuführen, die nicht im Mittelpunkt unserer Aufmerksamkeit steht. Überspringen Sie ihn.

Wir konzentrieren uns hauptsächlich auf die expand () -Methode dieser Dekorationsklasse, die in den Dokumentationskommentaren geschrieben ist:

Eine "Brute Force" -Methode zur Parametrisierung von Testfällen. Erstellt neue Testfälle und fügt sie in den Namespace ein, in dem die umschlossene Funktion definiert wird. Nützlich für die Parametrisierung von Tests in Unterklassen von 'UnitTest', in denen Nose-Testgeneratoren nicht funktionieren.

Die beiden wichtigsten Aktionen sind: "Erstellt neue Testfälle" und "Fügen Sie sie in den Namespace ein ...".

In Bezug auf den ersten Punkt ähnelt es ddt, mit Ausnahme einiger Unterschiede in den Namensstilen sowie unterschiedlicher Parameteranalyse und -bindung, die nicht allzu viel Aufmerksamkeit verdienen.

Am unterschiedlichsten ist, wie die neue Testmethode effektiv gemacht werden kann.

parametrisiert verwendet einen "Injektions" -Ansatz:

inspect Ist eine leistungsstarke Standardbibliothek, die hier verwendet wird, um Informationen über den Programmaufrufstapel abzurufen. Der Zweck der ersten drei Codezeilen besteht darin, f_locals zu entfernen, was "lokaler Namespace, der von diesem Frame gesehen wird" bedeutet, wobei sich f_locals auf den lokalen Namespace der Klasse bezieht.

Wenn Sie von lokalen Namespaces sprechen, denken Sie vielleicht an Einheimische (), aber wir haben in früheren Artikeln "Lese- und Schreibprobleme von Einheimischen () und Globalen ()" erwähnt, Einheimische () sind lesbar und nicht beschreibbar, daher dieser Absatz Der Code verwendet f_locals.

3. Wie erreicht pytest die Parametrisierung?

Schauen Sie sich zuerst die Schrift im vorherigen Artikel gemäß der Konvention an:

import pytest
@pytest.mark.parametrize("first,second", [(3,1), (-1,0), (1.5,1.0)])
def test_values(first, second):
    assert(first > second)

Sehen Sie zuerst "Markieren", einige Tags sind in pytest integriert, wie z. B. parametrisieren, timeout, skipif, xfail, tryfirst, trylast usw., unterstützen auch benutzerdefinierte Tags, Sie können Ausführungsbedingungen festlegen, Gruppenfilter ausführen und das ursprüngliche Testverhalten ändern Warten Sie.

Die Verwendung ist ebenfalls sehr einfach, der Quellcode kann jedoch viel komplizierter sein. Wir konzentrieren uns hier nur auf die Parametrisierung. Schauen Sie sich zunächst den Kern des Codes an:

Entsprechend den eingehenden Parameterpaaren werden die aufrufenden Informationen der ursprünglichen Testmethode kopiert und in der aufzurufenden Liste gespeichert. Im Gegensatz zu den beiden zuvor analysierten Bibliotheken werden hier keine neuen Testmethoden erstellt, sondern vorhandene Methoden wiederverwendet. Suchen Sie nach der Metafunc-Klasse, zu der parametrize () gehört, und verfolgen Sie, wo die _calls-Liste verwendet wird:

Schließlich wird es in der Funktionsklasse ausgeführt:

Interessant ist, dass wir hier ein paar Zeilen mit Gottesnotizen sehen können ...

Das Lesen (oberflächlich) des Quellcodes von pytest ist wirklich problematisch ... Es ist jedoch grob zu erkennen, dass bei der Parametrisierung das Schema des Generators verwendet wird und beim Durchlaufen eines Parameters die Testmethode einmal aufgerufen wird. Im vorherigen ddt und parametrisiert werden alle Parameter auf einmal analysiert, und n neue Testmethoden werden generiert und dann zur Planung an das Testframework übergeben.

Im Vergleich dazu sind die Ideen der ersten beiden Bibliotheken sehr klar, und da ihr Design im Gegensatz zu pytest nur zur Parametrisierung dient, gibt es keine Tags und zu viele abstrakte Designs, sodass es leichter zu lesen und zu verstehen ist. Die ersten beiden Bibliotheken nutzen die dynamischen Funktionen von Python, setzen Klassenattribute oder fügen lokale Namespaces ein, und pytest scheint eine ungeschickte Methode zu sein, um Ideen aus welcher statischen Sprache auszuleihen.

4. Die endgültige Zusammenfassung

Zurück zur Frage im Titel "Wie ändere ich eine Methode in mehrere Methoden?" Zusätzlich zu den parametrisierten Tests frage ich mich, welche anderen Szenarien diesen Reiz haben werden. Willkommen, um eine Nachricht zu hinterlassen, um zu diskutieren.

Dieser Artikel analysiert die Implementierung des Dekorators der drei Testbibliotheken. Durch Lesen des Quellcodes können wir feststellen, dass jede ihre eigenen Vorteile hat. Diese Entdeckung selbst ist sehr interessant. Bei der Verwendung von Dekorateuren unterscheiden sie sich an der Oberfläche nicht sehr, aber die Details des echten Kung Fu sind darunter verborgen.

Die Bedeutung der Quellcode-Analyse besteht darin, herauszufinden, warum dies so ist. Was können Leser auf dieser Erkundungsreise gewinnen? Lass uns zusammen reden! (PS: Senden Sie "Lerngruppe" im Hintergrund des öffentlichen Kontos "Python cat", um das Passwort der Gruppe zu erhalten.)

Veröffentlicht 17 Originalartikel · Like1 · Besuche 818

Ich denke du magst

Origin blog.csdn.net/weixin_45433031/article/details/105518951
Empfohlen
Rangfolge