Phpunit: Datenprovider werden vor setupBeforeClass ausgeführt

Erstellt am 21. Feb. 2013  ·  21Kommentare  ·  Quelle: sebastianbergmann/phpunit

Die Datenprovider werden aufgerufen, bevor das statische setupBeforeClass ausgeführt wurde. Wenn ich denke, es sollte umgekehrt sein.

Anwendungsfall:

  • Wir haben eine Adapterliste. Einige Adapter werden nur in bestimmten Umgebungen unterstützt (z. B. Linux vs. Win),
  • Ich möchte eine Liste der unterstützten Adapter einmal initialisieren und über einen Datenprovider an alle Tests übergeben,
  • Der sauberste Weg, den ich mir vorstellen kann, ist, eine statische Eigenschaft in setupBeforeClass zu initialisieren und den Anbieter diese Eigenschaft zurückgeben zu lassen

Das Problem besteht darin, dass die Eigenschaft beim Aufrufen des Anbieters nicht initialisiert wird.

Version gefunden: 3.7.14

Hilfreichster Kommentar

@epdenouden JIT? Testen Sie mit JIT für PHP oder weiß ich etwas nicht? Entschuldigung, ich bin etwas verwirrt; meinst du PHP7.x und PHP8.x oder PHPUnit7.x und PHPUnit8.x?!

@MAChitgarha Ich verstehe Ihre Verwirrung, da PHP8 einen Just-in-Time-_Compiler_ erhält. Ich habe den Begriff für die neue Datenprovider-Implementierung verwendet, die auf derselben Idee basiert: ein just-in-time _loader_ aka Lazy-Loading. Initialisieren Sie nur das, was Sie brauchen, wenn Sie es brauchen.

Dies wird mit der kommenden PHP-Version 8 JIT gut funktionieren, da sie keine kniffligen Funktionen der PHP-Sprache selbst verwendet. Meine ganze Arbeit steckt tief in PHPUnit, indem ich Dinge wie das Parsen der Konfiguration, das Laden und Ausführen von Tests usw.

Wenn es so aussieht, als würde ich nichts tun, mache ich es richtig. Mit Ausnahme dieser niedrigeren GCP- oder AWS-Rechnung. 💸

Alle 21 Kommentare

Ich bin mir nicht sicher, ob es wirklich sinnvoll ist, eine gewisse Reihenfolge zu erzwingen, damit statische Ressourcen in setupBeforeClass initialisiert werden können, wenn Sie bedenken, dass Datenprovider nicht statisch sein müssen oder sich sogar in derselben Testklasse befinden müssen.

Warum nicht einfach die Adapter faul initialisieren? Dann ist die Reihenfolge völlig egal.

Ohne Ausführung des Datenproviders wissen wir nicht, wie viele Tests eine Testmethode durchführt. In einer perfekten Welt würden wir B/C brechen und verlangen, dass Datenprovider Objekte sind, die eine Schnittstelle implementieren, die Countable . Auf diese Weise könnten wir diese Bedenken trennen und eine viel sauberere Situation haben.

@whatthejeff Das ist, was wir am Ende tun. Die Initialisierung im Konstruktor könnte jedoch etwas sauberer sein, da Parameter bei der Rückgabe von einem Anbieter in ein Array eingeschlossen werden müssen.

@sebastianbergmann ok ich verstehe, warum sie jetzt schon genannt werden, wie wäre es mit der Erwähnung im Dokument? (Ich kann eine PR öffnen, die sich ändert, wenn Sie in Ordnung sind). Ich bin mir nicht einmal sicher, ob es hilfreich wäre, Objekte zu haben: Wenn man sich meinen Ex anschaut, gibt es eine Abhängigkeit von einem Anbieter zum anderen.

Danke für eure Antworten, schließt das Problem.

@vicb , gerne

Ich bin vielleicht etwas spät dran, aber wenn ich Abhängigkeiten habe, die ich initialisieren muss, lasse ich die Anmerkung @dataProvider weg und verwende stattdessen yield .

@srosato Ich bin dumm (ich habe keine Rendite verwendet). Könnten Sie bitte ein kurzes Beispiel geben, wie das geht? Über https://gist.github.com, falls ein Beispiel hier nicht angebracht ist.

Wenn Ihr @dataProvider Setup-Code erfordert und sie nicht sehr groß sind, können Sie sie als Strings und eval im Test angeben. Zum Beispiel:

public function myProvider() {
    return [
        'new Klass("param 1", "param 2")',
        'new Klass("param 1", "param 2")',
    ];
}

/**
 * <strong i="8">@dataProvider</strong> myProvider
 */
public testMyFunction($instance_str) {
    $klass = eval("return {$instance_str};");
    # continue testing $klass ...
}

meine zwei Cent:

Anstatt dass sich ein Datenprovider auf eine statische Eigenschaft verlässt (von der wir wissen, dass sie nicht möglich ist), setze ich die erforderlichen Daten / Objekte als Klassenmitglied(er) der Testklasse in der Methode setUp() .
in tearDown() setze ich sie auf null, oder wenn es komplexe Objekte gibt, implementiere ich eine clear() Methode dafür.

Solange es nicht zu viele testklassenweite Abhängigkeiten gibt, fühle ich mich mit diesem Ansatz wohl.
Wenn es jedoch mehr als eine oder zwei solcher Abhängigkeiten gibt, müssen Sie Ihr Gesamtdesign möglicherweise überdenken.

Insbesondere verwende ich eine DB-Verbindung als statische Eigenschaft, die in setUpBeforeClass() und setze $queryBuilder , wodurch die Verbindung injiziert wird, als Klassenmitglied in setUp() (anstelle von an einen Datenprovider zurückgeben).

Eine Lösung wäre, eine neue private Methode zu definieren und so viele Variablen wie nötig zu definieren, wie static darin enthalten. Überprüfen Sie dann, ob einer von ihnen gesetzt ist, und wenn nicht, setzen Sie alle und geben Sie sie zurück (oder geben Sie sie zurück). Auf diese Weise werden _die Variablen nur einmal deklariert_, selbst wenn Sie zehn Provider oder was auch immer haben. Außerdem können Sie es in setUpBeforeClass() oder setUp() .

Sehen Sie es an einem Beispiel:

private static function getData()
{
    static $data, $anotherData;

    if (!isset($data)) {
        $data = new TestClass();
        $anotherData = [];
    }

    return [
        $data,
        // Or: clone $data
        $anotherData,
    ];
}

public static function setUpBeforeClass()
{
    list(self::$sampleJson, self::$sampleData) = self::getData(); 
}

public function sampleProvider()
{
    $data = self::getData();

    return [
        $data
    ];
}

@MAChitgarha Dank deines Kommentars gestern habe ich von diesem Ticket

Ich arbeite an der Umgestaltung der Datenproviderlogik in #3736, die einige der häufigsten Probleme lösen wird:

  • diese enorme Aktivitätsspitze zu Beginn des Laufs nicht mehr haben, da alle Datenprovider geladen werden
  • Datenprovider können nach den Fixtures setUpBeforeClass und setUp run laufen
  • nicht-statische Datenprovider werden möglich
  • Generatoren werden viel effizienter

@epdenouden Viel Spaß, das zu hören! ;) Der zweite Punkt in Ihrer Liste ist gut. Darauf warten.

Nicht-statische Datenprovider werden möglich

Ist das aktuell nicht möglich? Ich verwende in meinen Tests immer nicht statische Datenanbieter anstelle von statischen, und sie funktionieren wie erwartet ohne Probleme (mit PHPUnit 7.5.6). Liege ich falsch?

Ist das aktuell nicht möglich? Ich verwende in meinen Tests immer nicht statische Datenanbieter anstelle von statischen, und sie funktionieren wie erwartet ohne Probleme (mit PHPUnit 7.5.6). Liege ich falsch?

Nein, du hast recht! Meine Formulierung war nicht präzise genug, danke für die Erwähnung. Tief im Verarbeitungscode des Datenproviders, der derzeit gelesen wird:

private static function getDataFromDataProviderAnnotation($allTheParameters): ?iterable
    // code for locating the data provider
    // [...]
                if ($dataProviderMethod->isStatic()) {
                    $object = null;
                } else {
                    $object = $dataProviderClass->newInstance();
                }

    // code for preparing returned data rows
    // [...]
}

Hier ist also das schmutzige kleine Geheimnis:

  • Sie können nicht statische Datenanbieter verwenden und
  • sie werden bei einem tatsächlichen instanceof aufgerufen, aber...
  • es ist _nicht dieselbe Instanz_ die für die Fixtures und Tests verwendet wird

Sie haben mir etwas zum Nachdenken und Validieren in Bezug auf das Refactoring der Datenprovider gegeben! Ich muss zusätzliche Tests hinzufügen, um sicherzustellen, dass die Objektinstanzen die erwarteten sind und nicht nur derselbe Typ oder ein Klon. ☕️ und 🍰 auf mich, wenn du in Amsterdam bist.

@epdenouden Aber ich bekomme keine Ausfälle! In PHPUnit 7.5.13 erzeugt die Assertion keine Fehler. Wie kommt dieser Fehler? Rückwärts-Inkompatibilität, meinst du?

Dies ist in dem Lazy Loading Data Provider Branch, an dem ich arbeite und der auf 8.2 basiert. Ich werde als nächstes 7.5.x überprüfen, danke für die Vorwarnung.

Wenn dies funktioniert, muss ich zu einigen anderen älteren Problemen zurückkehren, die explizit nach der Implementierung nicht statischer Anbietermethoden fragten, und die genauen Anwendungsfälle erneut betrachten.

Auf jeden Fall: Dein Kommentar hat bereits einen sehr nützlichen Test inspiriert :)

Und @MAChitgarha ja das wäre ein BC-break

@epdenouden Ich warte darauf. Aus einigen Gründen verwende ich in meinem Projekt PHPUnit 7.5.13; aber ich werde es bald auf 8.2.* aktualisieren. Und gute Nachrichten, wenn der BC-Bruch behoben werden könnte. Ich kenne die Struktur der PHPUnit selbst nicht, daher weiß ich nicht, was Sie geändert haben, aber ich denke, es ist möglich, es zu beheben. Es liegt jedoch an Ihnen! :)

Und gute Nachrichten, wenn der BC-Bruch behoben werden könnte. Ich kenne die Struktur der PHPUnit selbst nicht, daher weiß ich nicht, was Sie geändert haben, aber ich denke, es ist möglich, es zu beheben. Es liegt jedoch an Ihnen! :)

Die Einführung einer Abwärtskompatibilitätsunterbrechung ohne sehr guten Grund ist etwas, das @sebastianbergmann nicht

Also ja, bitte sagen Sie uns, was Sie als Endbenutzer/Entwickler von Tests gerne sehen würden. Denken Sie nur daran, dass Projekte wie diese keinen großen Pool an freiwilligen Entwicklern haben.

Interessante Tatsache: Der obige Test funktioniert tatsächlich auf beiden Versionen 7.x und 8.x und schlägt beim JIT-Prototyp fehl.

Der Code, an dem ich arbeite, ähnelt immer noch viel mehr dem ursprünglichen Codefluss. Es könnte also das Ergebnis eines Fehlers sein, etwas Logik muss ich noch weiter umgestalten, etwas untergeordnetes über Objekte und Reflexionen in PHP, das ich nachschlagen muss, oder vielleicht nur ein naives Testen meinerseits. 🔬

@epdenouden JIT? Testen Sie mit JIT für PHP oder weiß ich etwas nicht? Entschuldigung, ich bin etwas verwirrt; meinst du PHP7.x und PHP8.x oder PHPUnit7.x und PHPUnit8.x?!

@epdenouden JIT? Testen Sie mit JIT für PHP oder weiß ich etwas nicht? Entschuldigung, ich bin etwas verwirrt; meinst du PHP7.x und PHP8.x oder PHPUnit7.x und PHPUnit8.x?!

@MAChitgarha Ich verstehe Ihre Verwirrung, da PHP8 einen Just-in-Time-_Compiler_ erhält. Ich habe den Begriff für die neue Datenprovider-Implementierung verwendet, die auf derselben Idee basiert: ein just-in-time _loader_ aka Lazy-Loading. Initialisieren Sie nur das, was Sie brauchen, wenn Sie es brauchen.

Dies wird mit der kommenden PHP-Version 8 JIT gut funktionieren, da sie keine kniffligen Funktionen der PHP-Sprache selbst verwendet. Meine ganze Arbeit steckt tief in PHPUnit, indem ich Dinge wie das Parsen der Konfiguration, das Laden und Ausführen von Tests usw.

Wenn es so aussieht, als würde ich nichts tun, mache ich es richtig. Mit Ausnahme dieser niedrigeren GCP- oder AWS-Rechnung. 💸

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

kunjalpopat picture kunjalpopat  ·  4Kommentare

stephen-leavitt-sonyatv-com picture stephen-leavitt-sonyatv-com  ·  4Kommentare

keradus picture keradus  ·  4Kommentare

sebastianbergmann picture sebastianbergmann  ·  3Kommentare

nicklevett picture nicklevett  ·  4Kommentare