Facebook
Twitter
Google+
Kommentare
12

Projektwerkstatt: Pasmine, assert library

Zweiter Tag meiner Elternzeit. Bis jetzt bin ich irgendwie noch im Arbeitsmodus, was aber auch klar ist. Könnte ja auch Wochenende sein. Mal schauen wenn mein Körper das merkt und auf entspannt umschaltet. Ich werde euch berichten.

Jetzt wo ich frei habe, möchte ich mich wieder ein wenig intensiver um LiveTest kümmern. Die Session Verwaltung ist so gut wie fertig. was der nächste große Schritt sein wird. Was dann aber kommt ist eine Verbesserung für die Testschreibenden sein. Da ich aber ungerne nur für ein Projekt programmiere, wird soll die entstehende Bibliothek eigenständig veröffentlicht werden. Nennen wir sie einfach Pasmine.

Pasmine soll eine Art Assert-Bibliothek sein. Die meisten von euch werden ja die Assert-Klasse bzw. -Methoden von Sebastian Bergmann kennen, die er in PHPUnit verbaut hat. Als Beispiel kann man hier wohl das einfachste Beispiel nehmen:

$this->assertEquals( $expected, $actual, 'The elemens are not the same');

Was beim Ausführen passiert ist ein Vergleich der beiden Elemente (expteced und actual), falls sie gleich sind passiert gar nichts, wenn sie sich unterscheiden, wird eine PHPUnit-Exception geworfen, mit dem Text „‚the elements are not the same“. Da wir bei LiveTest auch jede Menge solcher Prüfungen haben, brauchen wir etwas ähnliches (vielleicht sogar das gleiche).

Jetzt könnte man natürlich sagen: „komm wir nehmen die PHPUnit-Schnittstelle, dann haben wir die wenigsten Probleme„, das Rad neu erfinden und so. Irgendwie stören mich aber zwei Dinge. Zum einen die die Bibliothek sehr stark an PHPUnit gekoppelt und diese Abhängigkeit möchte ich nicht unbedingt in unserem Projekt auflösen müssen. Zum anderen finde ich die Syntax nicht ganz so toll. Naja ich fand sie eigentlich toll, bis ich auf der letzten IPC Jasmine kennengelernt habe. Ihr ahnt jetzt woher der Pasmine-Name kommt. Die Syntax, die dort verwendet wird, auf PHP und die Bibliothek übertragen, könnte wir folgt aussehen:

$this->expect($object1)->isEqualTo($object2)->ifNot('The elements are not the same');

Der Ansatz ist in sich eine DSL und wie ich finde relativ intuitiv zu verwenden. Damit hätte man natürlich meinen zweiten Kritikpunkt bei PHPUnit gelöst. Jetzt müssen wir uns noch um die Kopplung an die Exception kümmern, die ich bemängelt habe. Da

hatte ich folgende Idee:

$callback = function ($message) { throw new \Exception( $message); };
$pasmine = new Pasmine( $callback );

Die oben beschriebene ifNot-Methode, würde dann einfach den registrierten Callback nehmen und ihn mit der Nachricht ausführen. So könnte jeder Anwender selber bestimmen, wie der Bibliothek Hinweise geben soll, wenn etwas mal nicht stimmt.

So jetzt seid ihr wieder gefragt. Sind die Ideen allesamt doof? Sollte ich einfach auf PHPUnit setzen?Oder ist die Bibliothek vielleicht doch gar nicht so schlecht und ihr würdet die gerne sehen und auch in euren Projekten verwenden? Vielleicht habt ihr noch ein paar Tipps, wie die Syntax aussehen sollte oder was man zu beachten hat. Im Moment existiert nicht viel mehr als dieser Artikel zu dem Thema, man kann also alles umschmeißen, so wie man will. Also los. Feedback!

PS: Hamcrest anschauen lohnt sich auch.

Über den Autor

Nils Langner

Nils Langner ist der Gründer von "the web hates me" und auch der Hauptautor. Im wahren Leben leitet er das Qualitätsmanagementteam im Gruner+Jahr-Digitalbereich und ist somit für Seiten wie stern.de, eltern.de und gala.de aus Qualitätssicht verantwortlich. Nils schreibt seit den Anfängen von phphatesme, welches er ebenfalls gegründet hat, nicht nur für diverse Blogs, sondern auch für Fachmagazine, wie das PHP Magazin, die t3n, die c't oder die iX. Nebenbei ist er noch ein gern gesehener Sprecher auf Konferenzen. Herr Langner schreibt die Texte über sich gerne in der dritten Form.
Kommentare

12 Comments

  1. Finde deinen Ansatz ganz gut. Das mit dem Callback finde ich sinnvoll. Ich persönlich gehe immer sparsam mit Exceptions um. In einigen Situationen sind Exceptions gut (wenn der Nutzer die vorherigen Prüfmechanismen umgeht und trotzdem irgendwas ungewolltes z.B. über ein Formular absendet), aber in anderen Situationen (nur um zu melden das etwas nicht stimmt, z.B. beim serverseitigen verifizieren der E-Mail Adresse) ist es allerdings etwas überflüssig.

    Reply
  2. Ich gebe dir mal einen Tipp… genieße lieber die Zeit mit dem Baby, die Zeit geht so schnell rum. Das Baby wird dir die Zeit danken, der Computer nicht und wohl auch nicht die hier lesenden;-) oder vielleicht doch… ist ein super Blog:-)

    Reply
  3. Mein Vorschlag ist, sich so stark wie möglich am Vorbild zu orientieren, was die API angeht, also statt

    $this->expect($object1)->isEqualTo($object2)->ifNot(‚The elements are not the same‘);

    lieber

    $this->expect($object1)->toEqual($object2);

    Eine explizite Möglichkeit für Nachrichten würde ich gar nicht erst einbauen, wer will, kann ja passende Matcher (für Uneingeweihte: die Methode toEqual ist ein solcher) implementieren.

    Reply
  4. @GodsBoss: So habe ich auch angefangen, aber dann habe ich ja wieder komplexere Aufrufe:

    if( $this->expect($object1)->toEqual($object2) )
    {
    throw \Exception
    }

    oder

    try {
    $this->expect($object1)->toEqual($object2);
    }catch /\Pasmine\Exception $e) {
    throw \Exception
    }

    aber vielleicht gibt es da auch eine schöne Lösung, die ich gerade nicht sehe.

    Reply
  5. @Nils: Nein, wieso? Ich sehe das doch richtig, dass in deinem Beispiel die Gleichheit von $object1 und $object2 verglichen werden soll? Das wird mit

    $this->expect($object1)->toEqual($object2);

    schon erreicht. toEqual() ist in dem Fall zuständig dafür, den Mechanismus, der für die Ausgabe sorgt, zu benachrichtigen. Was willst du denn mit den geworfenen Exceptions überhaupt erreichen? In Jasmine werfen die Matcher keine Exceptions,

    expect(5).toEqual(6);
    expect(true).toBeFalsy();

    bei einem Aufruf von it() zeigt zwei fehlgeschlagene Erwartungen an.

    Reply
  6. @GodsBoss: Ok, ich glaube das hab ich verstanden. Dann würde ich das ifNot aber als optional trotzdem implementieren, falls man die Textmeldung einer Erwartung anpassen will.

    Reply
  7. @Nils:

    Würde ich nicht machen. Im Zweifelsfall sollten lieber eigene Matcher zum Einsatz kommen. Ich weiß nicht, wieviel du schon mit Jasmine gemacht hast, aber dort wird die Meldung aus dem Matcher-Namen generiert. Aus meinem Beispiel von vorher:

    expect(5).toEqual(6);

    ergibt „Expected 5 to equal 6.“ und

    expect(true).toBeFalsy();

    ergibt „Expected true to be falsy.“

    Wenn du den im Jasmine-Wiki angegebenen Matcher

    expect(yourCode).toBeLotsBetter();

    implementieren würdest, würdest du *keinerlei* Mechanismus zum Erzeugen der Nachricht schreiben, bei Nichterfüllung würde dort automatisch stehen „Expected * to be lots better.“ (* durch die Repräsentation von yourCode ersetzen).

    Wenn also die Nutzung von ifNot() nötig wäre, wären entweder die String-Repräsentationen der eingesetzten Objekte unverständlich oder der matcher-Name unpassend.

    Prinzipiell wäre natürlich ifNot() abwärtskompatibel, insofern würde ich einfach die Implementierung auf später verschieben. 😉

    Reply
  8. @Michael: Dem kann ich nur zustimmen. Die Zeit geht leider viel zu schnell vorbei und man kann die Uhr nicht zurückdrehen…

    Ich kenne Jasmine nicht, aber ich könnte mir folgende Ergänzung zu Deinen Gedanken vorstellen:
    $this->expect($object1)->isEqualTo($object2)->ifNot(‚The elements are not the same‘)->thenCallback($callback);

    Das hätte einige geschmeidige Vorteile. Du kannst die Action, die ausgelöst werden soll selbst definieren und z.B. eine Exception werfen. Außerdem könnte man (theoretisch) mehrere Fälle hinterlegen:
    $equal = $this->expect($object1)->isEqualTo($object2);
    $equal->ifNot(‚The elements are not the same‘)->thenCallback($equalFalse);
    $equal->ifTrue(‚The elements are not the same‘)->thenCallback($equalTrue);

    Ist aber nur so ein Gedanke…

    Statt ifNot() würde ich eher zu ifFalse() tendieren. Das ist aber wohl Geschmackssache.

    Reply
  9. @Tobias R.:

    Also, wenn Pasmine analog zu Jasmine wird, dann ergibt

    $this->expect($obj1)->toEqual($obj2);

    schon die Meldung „Expected $obj1 to equal $obj2.“ ($obj1 und $obj2 jeweils durch eine entsprechende String-Repräsentation ersetzen), falls $obj1 und $obj2 nicht gleich sind.

    Reply
  10. @Michael: Danke übrigens für den Tipp mit dem Kind, da das bloggen aber mein Hobby ist und ich es sehr gerne mache muss ich es nicht aufgeben. Das geht auch beides zusammen. Max muss ja schon um 8 ins Bett 😉

    Reply
  11. Lieber Nils,

    ich bin ein großer Fan deines Blogs und auch deiner Ideen.
    Allerdings würde ich dich bitten, wieder mehr auf deine Rechtschreibung zu achten.

    War jetzt nicht ganz leicht für mich den Text zu lesen 😉

    lg, Michael

    Reply
  12. Dass die Assert Parts in PHPOUnit so eng mit dem Kern verknüpft sind, sollte eigentlich kein Grund sein, dass direkt auszuschließen.
    Es wurde ja zuletzt viel dafür getan, die Bindungen zwischen den Komponenten zu entkoppeln. Das wäre als erster Schritt sicher einfacher und auch Produktiver als komplett neu schreiben. Deine eigene Abstraktion davon könntest du dann auf Grundlage der PHPUnit Assert Klassen schreiben.

    Reply

Leave a Comment.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen