Best Practices für PHPUnit zum Organisieren von Tests

73

Ich werde derzeit mit den PHPUNIT-Tests für ein Projekt von vorne beginnen. Also habe ich mir einige Projekte (wie Zend) angesehen, um zu sehen, wie sie Dinge tun und wie sie ihre Tests organisieren.

Die meisten Dinge sind ziemlich klar. Ich habe nur Probleme damit, wie die Testsuiten richtig organisiert werden. Zend hat eine AllTests.php, aus der andere Testsuiten geladen werden.
Wenn PHPUnit_Framework_TestSuiteman sich die Klasse ansieht, die zum Erstellen eines Suite-Objekts und zum Hinzufügen der anderen Suites verwendet wird, gibt es in den PHPUnit-Dokumenten zum Organisieren von Tests in PHPUnit-Versionen nach 3.4 nur eine Beschreibung für XML oder FileHierarchy. Derjenige, der Klassen zum Organisieren der Tests verwendet, wurde entfernt.
Ich habe nichts gefunden, was diese Methode veraltet macht und Projekte wie Zend verwenden sie immer noch.

Aber wenn es veraltet ist, wie könnte ich Tests in derselben Struktur wie die XML-Konfiguration organisieren? Das Ausführen aller Tests ist kein Problem, aber wie würde ich die Tests (in der XML) organisieren, wenn ich nur einige Tests ausführen wollte. Vielleicht mehrere xmls erstellen, in denen ich nur einige Tests / Testsuiten spezifiziere, die ausgeführt werden sollen?

Wenn ich also nur Modul1 und Modul2 der Anwendung testen möchte, hätte ich für jede eine zusätzliche XML und definiere Testsuiten nur für die Module (Klassen, die vom Modul verwendet werden). Und auch eine, die eine Testsuite für alle Tests definiert?

Oder wäre es besser, die @groupAnmerkung zu den spezifischen Tests zu verwenden, um sie als für Modul1 oder Modul2 zu markieren?

Vielen Dank im Voraus, dass Sie mich auf einige Best Practices hingewiesen haben.

enricog
quelle

Antworten:

112

Ich beginne mit einem Link zum Handbuch und gehe dann auf das ein, was ich auf dem Gebiet gesehen und gehört habe.

Organisation von Phpunit-Testsuiten

Organisation des Modul- / Testordners im Dateisystem

Mein empfohlener Ansatz ist das Kombinieren des Dateisystems mit einer XML-Konfiguration.

tests/
 \ unit/
 | - module1
 | - module2
 - integration/
 - functional/

mit einem phpunit.xmlmit einem einfachen:

<testsuites>
  <testsuite name="My whole project">
    <directory>tests</directory>
  </testsuite>
</testsuites>

Sie können die Testsuiten aufteilen, wenn Sie möchten, aber das ist eine Projekt-zu-Projekt-Auswahl.

Beim Ausführen phpunitwerden dann ALLE Tests ausgeführt, und beim Ausführen phpunit tests/unit/module1werden alle Tests von Modul1 ausgeführt.

Organisation des Ordners "Einheit"

Der häufigste Ansatz besteht darin, Ihre source/Verzeichnisstruktur in Ihrer tests/unit/Ordnerstruktur zu spiegeln .

Sie haben sowieso eine Testklasse pro Produktionsklasse, daher ist dies ein guter Ansatz in meinem Buch.

In der Dateiorganisation

  • Eine Klasse pro Datei.

Es wird sowieso nicht funktionieren, wenn Sie mehr als eine Testklasse in einer Datei haben, also vermeiden Sie diese Gefahr.

  • Ich habe keinen Test-Namespace

Es macht das Schreiben des Tests nur ausführlicher, da Sie eine zusätzliche use-Anweisung benötigen. Ich würde also sagen, dass die testClass im selben Namespace wie die Produktionsklasse abgelegt werden sollte, aber das ist nichts, wozu PHPUnit Sie zwingt. Ich habe gerade festgestellt, dass es einfacher ist, ohne Nachteile.

Nur wenige Tests ausführen

phpunit --filter FactoryFührt beispielsweise alle FactoryTests aus, während alle mit der phpunit tests/unit/logger/Protokollierung verbundenen Elemente ausgeführt werden.

Sie können @groupTags für Ausgabenummern, Storys oder ähnliches verwenden, aber für "Module" würde ich das Ordnerlayout verwenden.

Mehrere XML-Dateien

Es kann nützlich sein, mehrere XML-Dateien zu erstellen, wenn Sie Folgendes möchten:

  • eine ohne Code-Abdeckung
  • eine nur für die Unit-Tests (aber nicht für die Funktions- oder Integrations- oder Langzeittests)
  • andere häufige "Filter" -Fälle
  • PHPBB3 zum Beispiel macht das für their phpunit.xmls

Codeabdeckung für Ihre Tests

Wie es mit dem Starten eines neuen Projekts mit Tests zusammenhängt:

  • Mein Vorschlag ist, @coversTags wie in meinem Blog beschrieben zu verwenden (Nur für Komponententests, decken Sie immer alle nicht öffentlichen Funktionen ab, verwenden Sie immer Cover-Tags.
  • Generieren Sie keine Abdeckung für Ihre Integrationstests. Es gibt Ihnen ein falsches Gefühl der Sicherheit.
  • Verwenden Sie immer die Whitelist, um Ihren gesamten Produktionscode einzuschließen, damit die Zahlen Sie nicht anlügen!

Autoloading und Bootstrapping Ihrer Tests

Sie benötigen für Ihre Tests kein automatisches Laden. PHPUnit wird sich darum kümmern.

Verwenden Sie das <phpunit bootstrap="file">Attribut, um Ihren Test-Bootstrap anzugeben. tests/bootstrap.phpist ein schöner Ort, um es auszudrücken. Dort können Sie Ihren Anwendungs-Autoloader usw. einrichten (oder Ihren Anwendungs-Bootstrap aufrufen).

Zusammenfassung

  • Verwenden Sie die XML-Konfiguration für so ziemlich alles
  • Separate Unit- und Integrationstests
  • Ihre Unit-Test-Ordner sollten die Struktur Ihres Anwendungsordners widerspiegeln
  • Um nur bestimmte Tests auszuführen, verwenden Sie phpunit --filteroderphpunit tests/unit/module1
  • Verwenden Sie den strictModus von Anfang an und schalten Sie ihn niemals aus.

Beispielprojekte zum Anschauen

edorian
quelle
@ TheCandyMan Es ist mir ein Vergnügen; Fühlen Sie sich frei, dem PHP-Chat auf SO oder freenode #phpunit beizutreten, wenn Sie auf Probleme damit
stoßen
@cmt Danke für das Update. Ich habe den Link im Beitrag zu meinem neuen Blog-Standort aktualisiert : edorian.github.io/… - Prost!
Edorian
@edorian wo ich die phpunit.xml Datei platzieren soll und wo ich die xml Datei aufrufen soll.
Kumar Shanmugam
4

Grundlegende Verzeichnisstruktur :

Ich habe versucht, den Testcode direkt neben dem zu testenden Code zu halten, buchstäblich im selben Verzeichnis mit einem etwas anderen Dateinamen als die Datei mit dem zu testenden Code. Bisher gefällt mir dieser Ansatz. Die Idee ist, dass Sie keine Zeit und Energie aufwenden müssen, um die Verzeichnisstruktur zwischen Ihrem Code und Ihrem Testcode synchron zu halten. Wenn Sie also den Namen des Verzeichnisses ändern, in dem sich der Code befindet, müssen Sie nicht auch den Verzeichnisnamen für den Testcode suchen und ändern. Dies führt auch dazu, dass Sie weniger Zeit damit verbringen, nach dem Testcode zu suchen, der zu einem Code gehört, da er sich direkt daneben befindet. Dies macht es sogar weniger mühsam, die Datei mit dem Testcode zu erstellen, da Sie nicht zuerst das Verzeichnis mit den Tests finden müssen. Erstellen Sie möglicherweise ein neues Verzeichnis, das mit dem Verzeichnis übereinstimmt, für das Sie Tests erstellen, und erstellen Sie dann die Testdatei. Sie erstellen einfach die Testdatei genau dort.

Ein großer Vorteil davon ist, dass die anderen Mitarbeiter (nicht Sie, weil Sie dies niemals tun würden) das Schreiben von Testcode zunächst weniger vermeiden, weil es einfach zu viel Arbeit ist. Selbst wenn sie Methoden zu vorhandenen Klassen hinzufügen, ist es weniger wahrscheinlich, dass sie keine Tests zum vorhandenen Testcode hinzufügen möchten, da das Auffinden des Testcodes nur wenig reibungslos funktioniert.

Ein Nachteil ist, dass es schwieriger ist, Ihren Produktionscode ohne die dazugehörigen Tests freizugeben. Wenn Sie strenge Namenskonventionen verwenden, ist dies möglicherweise dennoch möglich. Zum Beispiel habe ich ClassName.php, ClassNameUnitTest.php und ClassNameIntegrationTest.php verwendet. Wenn ich alle Unit-Tests ausführen möchte, gibt es eine Suite, die nach Dateien sucht, die auf UnitTest.php enden. Die Integrationstestsuite funktioniert ähnlich. Wenn ich wollte, könnte ich eine ähnliche Technik verwenden, um zu verhindern, dass die Tests für die Produktion freigegeben werden.

Ein weiterer Nachteil dieses Ansatzes besteht darin, dass bei der Suche nach tatsächlichem Code und nicht nach Testcode etwas mehr Aufwand erforderlich ist, um zwischen beiden zu unterscheiden. Aber ich denke, dass dies tatsächlich eine gute Sache ist, da es uns zwingt, den Schmerz der Realität zu spüren, dass Testcode auch Code ist, es seine eigenen Wartungskosten hinzufügt und genauso wichtig ein Teil des Codes ist wie alles andere, nicht nur irgendwo etwas abseits.

Eine Testklasse pro Klasse:

Dies ist für die meisten Programmierer alles andere als experimentell, aber für mich. Ich experimentiere damit, dass nur eine Testklasse pro Klasse getestet wird. In der Vergangenheit hatte ich für jede getestete Klasse ein ganzes Verzeichnis und dann mehrere Klassen in diesem Verzeichnis. Jede Testklasse richtete die zu testende Klasse auf eine bestimmte Art und Weise ein und ließ dann eine Reihe von Methoden mit jeweils einer anderen Behauptung erstellen. Aber dann bemerkte ich, dass bestimmte Bedingungen, in die ich diese Objekte bringen würde, Dinge mit anderen Bedingungen gemeinsam hatten, in die sie aus anderen Testklassen gerieten. Die Duplizierung ist zu umfangreich, daher habe ich angefangen, Abstraktionen zu erstellen, um sie zu entfernen. Der Testcode wurde sehr schwer zu verstehen und zu pflegen. Ich erkannte das, aber ich konnte keine Alternative sehen, die für mich Sinn machte. Nur eine Testklasse pro Klasse zu haben, schien nicht in der Lage zu sein, fast genug Situationen zu testen, ohne überwältigend zu werden, den gesamten Testcode in einer Testklasse zu haben. Jetzt habe ich eine andere Perspektive. Selbst wenn ich recht hatte, ist dies ein großer Dämpfer für andere Programmierer und mich, die die Tests schreiben und pflegen wollen. Jetzt experimentiere ich damit, mich zu zwingen, eine Testklasse pro getesteter Klasse zu haben. Wenn ich in dieser einen Testklasse auf zu viele Dinge stoße, um sie zu testen, experimentiere ich damit, dass dies ein Hinweis darauf ist, dass die getestete Klasse zu viel tut und in mehrere Klassen aufgeteilt werden sollte. Um Duplikate zu entfernen, versuche ich, mich so weit wie möglich an einfachere Abstraktionen zu halten, damit alles in einer lesbaren Testklasse vorhanden ist.

AKTUALISIEREN Ich verwende und mag diesen Ansatz immer noch, aber ich habe eine sehr gute Technik gefunden, um die Menge an Testcode und die Menge an Duplikaten zu reduzieren. Es ist wichtig, wiederverwendbare Assertionsmethoden in die Testklasse selbst zu schreiben, die von den Testmethoden in dieser Klasse häufig verwendet werden. Es hilft mir, die richtigen Arten von Assertionsmethoden zu finden, wenn ich sie als interne DSLs betrachte (etwas, das Onkel Bob fördert, und tatsächlich fördert er das Erstellen interner DSLs). Manchmal können Sie dieses DSL-Konzept sogar noch weiter verfolgen (tatsächlich ein DSL erstellen), indem Sie einen Zeichenfolgenparameter akzeptieren, der einen einfachen Wert hat, der sich darauf bezieht, welche Art von Test Sie durchführen möchten. Zum Beispiel habe ich einmal eine wiederverwendbare Assertionsmethode erstellt, die einen $ left-, $ compareesAs- und einen $ right-Parameter akzeptiert.$this->assertCmp('a', '<', 'b').

Ehrlich gesagt kann ich diesen Punkt nicht genug betonen, es ist die gesamte Grundlage, um Schreibtests zu etwas Nachhaltigem zu machen (das Sie und die anderen Programmierer weiterhin tun möchten). Es macht es möglich, dass der Wert, den Tests hinzufügen, überwiegt, was sie wegnehmen. Der Punkt ist nicht, dass Sie genau diese Technik verwenden müssen, der Punkt ist, dass Sie eine Art wiederverwendbarer Abstraktionen verwenden müssen, mit denen Sie kurze und lesbare Tests schreiben können. Es mag so aussehen, als würde ich von der Frage abweichen, aber das bin ich wirklich nicht. Wenn Sie dies nicht tun, werden Sie schließlich in die Falle geraten, mehrere Testklassen pro getesteter Klasse erstellen zu müssen, und die Dinge brechen von dort aus wirklich zusammen.

still_dreaming_1
quelle
Bisher funktioniert das für mich gut. Ich konnte meine Testklassen und die getesteten Klassen so einfach halten, dass ich nur eine Testklasse pro getesteter Klasse benötige. Das Beibehalten der Testklassen mit den anderen Klassen funktioniert ebenfalls gut. Jetzt experimentiere ich damit, diese Testklassen einfach "* Test.php" anstelle von "* UnitTest.php" und "* IntegrationTest.php" aufzurufen. Anstatt Unit- und Integrationstests zu haben, habe ich nur Tests. Sie ähneln eher Integrationstests, wenn dies dazu beiträgt, Verspottungen zu vermeiden. Die Tests werden immer sehr schnell ausgeführt, auch wenn sie tatsächlich mit einer Datenbank
funktionieren