Heim >Backend-Entwicklung >PHP-Tutorial >Schreiben hochwertiger Tests
Leider erhalten Tests in vielen Organisationen immer noch nicht die Aufmerksamkeit, die sie verdienen würden. Manchmal fühlt es sich so an, als hätten Entwickler ein schlechtes Gewissen, wenn sie keine Tests schreiben, und gleichzeitig wird der Testcode oft nicht ordnungsgemäß überprüft. Stattdessen wird in einer Rezension oft nur geprüft, ob es Tests gibt, was schade ist, denn nur Tests zu haben ist nicht gut genug. Eigentlich sollten sie mindestens die gleiche Qualität wie der gesamte andere Code in einem Projekt haben, wenn nicht sogar von höherer Qualität. Andernfalls könnten Tests Sie tatsächlich behindern, da Tests viel zu oft fehlschlagen, schwer zu verstehen sind oder viel zu lange in der Ausführung dauern. Einige dieser Punkte habe ich bereits in meinem Blogbeitrag über die Verwendung von In-Memory-Implementierungen anstelle von Repository-Mocks besprochen. Jetzt möchte ich einige andere, allgemeinere Dinge besprechen, auf die ich beim Schreiben von Tests achte.
Stack Overflow fordert Sie auf, den Fragen minimale, reproduzierbare Beispiele hinzuzufügen, und meiner Meinung nach ist dies aus genau den gleichen Gründen auch ein sehr guter Rat für das Schreiben von Tests. Besonders wenn man einen Test Monate nach dem Schreiben liest, ist es viel einfacher, vollständig zu verstehen, was passiert, wenn weniger Dinge passieren. Schreiben Sie also nur den Code, der für den Test unbedingt erforderlich ist, und widerstehen Sie der Versuchung, weitere Dinge hinzuzufügen, nur weil es einfach ist. Aber der Testcode muss natürlich trotzdem vollständig sein, d.h. ein Test sollte so viele Zeilen wie nötig, aber so wenig wie möglich enthalten.
Das mag eine unpopuläre Meinung sein, aber ich denke, dass es absolut sinnvoll ist, eine 100-prozentige Codeabdeckung anzustreben, auch wenn viele dies offenbar für eine schlechte Praxis halten.
Manchmal geben sich Teams mit einem niedrigeren Wert zufrieden, z.B. eine Codeabdeckung von 90 %. Allerdings macht das für mich wenig Sinn. Erstens sind alle diese Zahlen etwas willkürlich und schwer mit Daten zu belegen. Außerdem muss beim Schreiben von neuem Code nicht alles getestet werden, um diesen Schwellenwert zu erreichen. Und wenn es jemandem gelingt, die Abdeckung zu erhöhen, könnte die nächste Person damit durchkommen, überhaupt keine Tests zu schreiben und trotzdem eine Codeabdeckung von über 90 % beizubehalten, was zu einem falschen Vertrauensgefühl führt.
Eine der Ausreden, die ich oft höre, ist, dass es keinen Sinn macht, Tests für einfache Funktionen wie Getter und Setter zu schreiben. Und vielleicht überraschenderweise stimme ich dem voll und ganz zu. Aber hier ist der Haken: Wenn keiner der Tests tatsächlich diese Getter und Setter verwendet, besteht wahrscheinlich keine Notwendigkeit, sie zu haben. Anstatt sich also darüber zu beschweren, wie schwierig es ist, eine 100-prozentige Testabdeckung zu erreichen, Es wäre höchstwahrscheinlich besser, keinen Code zu schreiben, der überhaupt nicht benötigt wird. Dadurch wird auch der Wartungsaufwand vermieden, den jede Codezeile mit sich bringt.
Es gibt jedoch einen kleinen Haken: Manchmal macht Code seltsame Dinge, die dazu führen können, dass Code-Coverage-Tools einige Zeilen als nicht abgedeckt markieren, obwohl sie während des Testlaufs ausgeführt wurden. Ich bin nicht oft auf solche Situationen gestoßen, aber wenn es keine Möglichkeit gibt, dies zum Laufen zu bringen, schließe ich sie von der Codeabdeckung aus. Z.B. PHPUnit ermöglicht dies mithilfe der Annotation codeCoverageIgnore:
<?php class SomeClass { /** * @codeCoverageIgnore */ public function doSomethingNotDetectedAsCovered() { } }
Auf diese Weise wird diese Funktion nicht in die Codeabdeckungsanalyse einbezogen, was bedeutet, dass es immer noch möglich ist, eine Codeabdeckung von 100 % zu erreichen, und ich überprüfe diesen Wert auch weiterhin. Die Alternative besteht darin, sich mit einem niedrigeren Wert als 100 % zufrieden zu geben, aber dann gibt es die gleichen Probleme wie oben: Anderer Code wird möglicherweise auch nicht von Tests abgedeckt und wird möglicherweise übersehen.
Davon abgesehen gibt eine 100-prozentige Codeabdeckung sicherlich keine Garantie dafür, dass Ihr Code keine Fehler aufweist. Wenn Ihr Anwendungscode jedoch nicht abgedeckte Zeilen enthält, geben Sie Ihren Tests nicht einmal eine Änderung, um potenzielle Fehler in dieser Zeile zu erkennen.
Der Grund, warum Tests geschrieben werden, besteht darin, dass wir ein bestimmtes Verhalten des Codes sicherstellen möchten. Daher sind Behauptungen ein sehr wesentlicher Bestandteil des Testens.
Natürlich ist die wichtigste Überlegung beim Schreiben von Behauptungen, dass das Verhalten des Codes korrekt getestet wird. Aber eine sehr knappe Sekunde ist, wie sich die Behauptung verhält, wenn der Code fehlschlägt. Wenn eine Behauptung aus irgendeinem Grund fehlschlägt, sollte das Problem für den Entwickler so offensichtlich wie möglich sein. Eine Situation, in der dies offensichtlich ist, ist die Situation, an der derzeit in diesem Symfony-Pull-Request gearbeitet wird. Symfony verfügt über eine AssertResponseStatusCodeSame-Methode, die es ermöglicht, den Statuscode einer Antwort in einem Funktionstest zu überprüfen:
<?php declare(strict_types=1); class LoginControllerTest extends WebTestCase { public function testFormAttributes(): void { $client = static::createClient(); $client->request('GET', '/login'); $this->assertResponseStatusCodeSame(200); $this->assertSelectorCount(1, 'input[name="email"][required]'); } }
Das Problem bei diesem Test ist die Ausgabe, die er generiert, falls der Statuscode nicht 200 ist. Da Tests normalerweise in einer Entwicklungsumgebung ausgeführt werden, gibt Symfony eine Fehlerseite zurück, wenn auf diese URL zugegriffen wird, und die Methode „assertResponseStatusCodeSame“ gibt die aus gesamte Antwort für den Fall, dass die Behauptung fehlschlägt. Diese Ausgabe ist unglaublich lang, da sie nicht nur HTML, sondern auch CSS und JavaScript zurückgibt und mein Scrollback-Puffer buchstäblich zu klein ist, als dass ich die gesamte Nachricht lesen könnte.
Das ist absolut das schlechteste Beispiel, das mir bisher begegnet ist, aber es kann auch ärgerlich sein, wenn im Code die falschen Behauptungen verwendet werden. Schauen wir uns die Ausgabe der obigen Assertion „assetSelectorCount“ an, die mit der folgenden Meldung fehlschlägt, wenn der angegebene Selektor nicht genau ein Element liefert:
Failed asserting that the Crawler selector "input[name="email"][required]" was expected to be found 1 time(s) but was found 0 time(s).
Es gibt eine ziemlich gute Vorstellung von dem auftretenden Problem. Die Behauptung könnte jedoch auch anders geschrieben werden (tun Sie dies nicht zu Hause!):
<?php class SomeClass { /** * @codeCoverageIgnore */ public function doSomethingNotDetectedAsCovered() { } }
Jemand könnte argumentieren, dass dies genau das Gleiche bewirkt und es daher egal ist, welche Variante verwendet wird. Dies könnte nicht weiter von der Wahrheit entfernt sein, da die folgende Meldung angezeigt wird, wenn für eine E-Mail kein einziges erforderliches Eingabefeld vorhanden ist:
<?php declare(strict_types=1); class LoginControllerTest extends WebTestCase { public function testFormAttributes(): void { $client = static::createClient(); $client->request('GET', '/login'); $this->assertResponseStatusCodeSame(200); $this->assertSelectorCount(1, 'input[name="email"][required]'); } }
Das hilft überhaupt nicht, und wer an der Behebung des Problems arbeitet, muss zunächst einmal herausfinden, wo das Problem eigentlich liegt. Dies zeigt, dass immer eine passende Behauptung verwendet werden sollte, und PHPUnit verfügt über viele Behauptungen, die für alle Arten von Anwendungsfällen geeignet sind. Manchmal ist es sogar sinnvoll, eine benutzerdefinierte Behauptung zu erstellen.
Eine relativ neue Behauptung, die meiner Meinung nach in den letzten Jahren immer beliebter wurde, ist das Testen von Schnappschüssen. Besonders wenn man mit der Arbeit an einem Frontend-Projekt beginnt, scheint es sehr hilfreich zu sein. Ich habe es in der Vergangenheit oft mit React verwendet. Das Wesentliche ist, dass Ihre Tests etwa so aussehen:
Failed asserting that the Crawler selector "input[name="email"][required]" was expected to be found 1 time(s) but was found 0 time(s).
Die Magie geschieht in der toMatchSnapshot-Methode. Im allerersten Durchlauf schreibt es den Inhalt der Baumvariablen in eine separate Datei. Bei nachfolgenden Durchläufen vergleicht es den neuen Wert des Baumwerts mit dem, was es zuvor in seiner separaten Datei gespeichert hat. Wenn sich etwas ändert, schlägt der Test fehl und es wird ein Unterschied angezeigt, mit der Option, den Snapshot erneut zu aktualisieren, was bedeutet, dass Sie Ihre Tests im Handumdrehen reparieren können.
Das hört sich zwar wirklich gut an, bringt aber auch einige Nachteile mit sich. Erstens sind Snapshots ziemlich anfällig, da die Tests immer dann fehlschlagen, wenn sich das gerenderte Markup der Komponente ändert. Zweitens wird die Absicht des Tests verborgen, da er nicht erklärt, was der Autor eigentlich testen wollte.
Was mir jedoch wirklich gut gefallen hat, war, dass ich jedes Mal, wenn ich eine Komponente änderte, an alle anderen Komponenten erinnert wurde, die diese Komponente verwendeten, da alle diese Snapshots beim nächsten Durchlauf fehlschlugen. Aus diesem Grund gefiel es mir, mindestens einen Snapshot-Test pro Komponente durchzuführen.
Zusammenfassend denke ich, dass es ein paar Dinge gibt, mit denen Sie sofort beginnen können, um die Qualität Ihrer Tests zu verbessern:
Meiner Meinung nach wird das Befolgen dieser wenigen Regeln bereits einen großen Unterschied machen und dazu beitragen, dass Sie lange Freude an der Arbeit in der Codebasis haben!
Das obige ist der detaillierte Inhalt vonSchreiben hochwertiger Tests. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!