Paralleltest für PHPUnit mit ParaTest

PHPUnit weist seit 2007 auf Parallelität hin, aber in der Zwischenzeit laufen unsere Tests weiterhin langsam. Zeit ist Geld, richtig? ParaTest ist ein Tool, das sich auf PHPUnit befindet und das parallele Ausführen von Tests ohne Verwendung von Erweiterungen ermöglicht. Dies ist ein idealer Kandidat für Funktionstests (d. H. Selenium) und andere langwierige Prozesse.


ParaTest zu Ihren Diensten

ParaTest ist ein robustes Befehlszeilentool zum parallelen Ausführen von PHPUnit-Tests. Inspiriert von den feinen Leuten von Sauce Labs, wurde es ursprünglich entwickelt, um eine vollständigere Lösung zur Verbesserung der Geschwindigkeit von Funktionstests zu sein.

Seit seiner Gründung - und dank einiger brillanter Mitarbeiter (einschließlich Giorgio Sironi, dem Verwalter der PHPUnit Selenium-Erweiterung) - hat sich ParaTest zu einem wertvollen Werkzeug für die Beschleunigung von Funktionstests sowie für Integrationstests mit Datenbanken, Webservices und Dateisystemen entwickelt.

ParaTest hat auch die Ehre, mit dem Test-Framework Sausage von Sauce Labs gebündelt zu werden, und wurde zum Zeitpunkt dieses Schreibens in fast 7000 Projekten verwendet.

ParaTest installieren

Derzeit ist die einzige offizielle Methode zur Installation von ParaTest die Verwendung von Composer. Für diejenigen von Ihnen, die neu bei Composer sind, haben wir einen großartigen Artikel zu diesem Thema. Um die neueste Entwicklungsversion abzurufen, fügen Sie Folgendes in Ihre composer.json Datei:

 "erfordern": "brianium / paratest": "dev-master"

Alternativ für die neueste stabile Version:

 "erfordern": "brianium / paratest": "0.4.4"

Als nächstes ausführen Komponist installieren von der Kommandozeile aus. Die ParaTest-Binärdatei wird im erstellt Verkäufer / bin Verzeichnis.

Die ParaTest-Befehlszeilenschnittstelle

ParaTest enthält eine Befehlszeilenschnittstelle, die den meisten PHPUnit-Benutzern vertraut sein sollte, mit zusätzlichen Boni für das parallele Testen.

Ihr erster paralleler Test

Die Verwendung von ParaTest ist genauso einfach wie PHPUnit. Um dies schnell in Aktion zu demonstrieren, erstellen Sie ein Verzeichnis, Paratest-Probe, mit der folgenden Struktur:

Installieren wir ParaTest wie oben erwähnt. Vorausgesetzt, Sie haben eine Bash-Shell und eine global installierte Composer-Binärdatei, können Sie dies in einer Zeile von der Paratest-Probe Verzeichnis:

 echo '"erfordert": "brianium / paratest": "0.4.4"'> composer.json && composer install

Erstellen Sie für jede der Dateien im Verzeichnis eine Testfallklasse mit demselben Namen wie folgt:

 class SlowOneTest erweitert PHPUnit_Framework_TestCase public function test_long_running_condition () sleep (5); $ this-> assertTrue (true); 

Beachten Sie die Verwendung von schlaf (5) einen Test zu simulieren, dessen Ausführung fünf Sekunden dauert. Wir sollten also fünf Testfälle haben, die jeweils fünf Sekunden dauern. Mit Vanilla PHPUnit werden diese Tests seriell ausgeführt und dauern insgesamt 25 Sekunden. ParaTest führt diese Tests gleichzeitig in fünf separaten Prozessen aus und sollte nur fünf Sekunden dauern, nicht fünfundzwanzig!

Nachdem wir nun wissen, was ParaTest ist, wollen wir uns ein wenig mit den Problemen beschäftigen, die mit der parallelen Ausführung von PHPUnit-Tests verbunden sind.


Das anstehende Problem

Das Testen kann ein langsamer Prozess sein, insbesondere wenn wir davon sprechen, eine Datenbank zu treffen oder einen Browser zu automatisieren. Um schneller und effizienter testen zu können, müssen wir in der Lage sein, unsere Tests gleichzeitig (gleichzeitig) und nicht seriell (hintereinander) auszuführen..

Die allgemeine Methode, um dies zu erreichen, ist keine neue Idee: Führen Sie verschiedene Testgruppen in mehreren PHPUnit-Prozessen aus. Dies kann leicht mit der nativen PHP-Funktion erreicht werden proc_open. Das Folgende wäre ein Beispiel dafür in Aktion:

 / ** * $ runningTests - derzeit offene Prozesse * $ loadedTests - ein Array von Testpfaden * $ maxProcs - die Gesamtzahl der Prozesse, die ausgeführt werden sollen * / while (sizeof ($ runningTests) || sizeof ($ loadedTests)) while (sizeof ($ loadedTests) && sizeof ($ runningTests) < $maxProcs) $runningTests[] = proc_open("phpunit " . array_shift($loadedTests), $descriptorspec, $pipes); //log results and remove any processes that have finished… 

Da PHP keine nativen Threads fehlen, ist dies eine typische Methode, um eine gewisse Parallelität zu erreichen. Die besonderen Herausforderungen von Testwerkzeugen, die diese Methode verwenden, können auf drei Kernprobleme reduziert werden:

  • Wie laden wir Tests??
  • Wie aggregieren und berichten wir Ergebnisse aus den verschiedenen PHPUnit-Prozessen??
  • Wie können wir Konsistenz mit dem Originalwerkzeug (d. H. PHPUnit) gewährleisten??

Schauen wir uns ein paar Techniken an, die in der Vergangenheit angewendet wurden, und werfen Sie einen Blick auf ParaTest und wie es sich vom Rest der Masse unterscheidet.


Diejenigen, die vorher gekommen sind

Wie bereits erwähnt, ist die Idee, PHPUnit in mehreren Prozessen auszuführen, nicht neu. Das typische Verfahren ist in etwa wie folgt:

  • Grep für Testmethoden oder laden Sie ein Verzeichnis mit Dateien, die Testsuiten enthalten.
  • Öffnen Sie einen Prozess für jede Testmethode oder Suite.
  • Analysiert die Ausgabe aus der STDOUT-Pipe.

Schauen wir uns ein Tool an, das diese Methode verwendet.

Hallo, Paraunit

Paraunit war der ursprüngliche parallele Läufer, der mit dem Sausage-Tool von Sauce Labs mitgeliefert wurde, und diente als Ausgangspunkt für ParaTest. Schauen wir uns an, wie die drei oben genannten Probleme gelöst werden.

Test laden

Paraunit wurde entwickelt, um die Funktionsprüfung zu erleichtern. Es führt jede Testmethode und nicht eine gesamte Testsuite in einem eigenen PHPUnit-Prozess aus. Da der Pfad zu einer Sammlung von Tests angegeben ist, sucht Paraunit anhand von Musterabgleich mit dem Dateiinhalt nach individuellen Testmethoden.

 preg_match_all ("/ function (test [^ \ (] +) \ (/", $ fileContents, $ stimmt überein);

Geladene Testmethoden können dann wie folgt ausgeführt werden:

 proc_open ("phpunit --filter = $ testName $ testFile", $ descriptorspec, $ Pipes);

In einem Test, bei dem jede Methode einen Browser einrichtet und abbaut, kann dies die Sache erheblich beschleunigen, wenn jede dieser Methoden in einem separaten Prozess ausgeführt wird. Bei dieser Methode gibt es jedoch einige Probleme.

Während Methoden, die mit dem Wort beginnen, "Prüfung,"ist eine strikte Konvention unter PHPUnit-Benutzern, Anmerkungen sind eine weitere Option. Die von Paraunit verwendete Lademethode würde diesen vollkommen gültigen Test überspringen:

 / ** * @test * / public function twoTodosCheckedShowsCorrectClearButtonText () $ this-> todos-> addTodos (array ('one', 'two')); $ this-> todos-> getToggleAll () -> click (); $ this-> assertEquals ('2 abgeschlossene Objekte löschen', $ this-> todos-> getClearButton () -> text ()); 

Zusätzlich zur Unterstützung von Testanmerkungen ist die Vererbung auch begrenzt. Wir mögen die Vorzüge eines solchen Vorbringens argumentieren, aber betrachten wir das folgende Setup:

 abstrakte Klasse TodoTest erweitert PHPUnit_Extensions_Selenium2TestCase protected $ browser = null; public function setUp () // configure browser öffentliche Funktion testTypingIntoFieldAndHittingEnterAddsTodo () // Selenmagie / ** * ChromeTodoTest.php * Keine Testmethoden zum Lesen! * / class ChromeTodoTest erweitert TodoTest protected $ browser = 'chrome';  / ** * FirefoxTodoTest.php * Keine Testmethoden zum Lesen! * / class FirefoxTodoTest erweitert TodoTest protected $ browser = 'firefox'; 

Die geerbten Methoden befinden sich nicht in der Datei und werden daher niemals geladen.

Ergebnisse anzeigen

Paraunit fasst die Ergebnisse jedes Prozesses zusammen, indem er die von jedem Prozess generierte Ausgabe analysiert. Mit dieser Methode kann Paraunit die gesamte Bandbreite der von PHPUnit präsentierten Kurzcodes und Feedbacks erfassen.

Der Nachteil der Aggregation von Ergebnissen auf diese Weise ist, dass sie ziemlich unhandlich und leicht zu brechen ist. Es gibt viele unterschiedliche Ergebnisse und viele reguläre Ausdrücke bei der Arbeit, um sinnvolle Ergebnisse auf diese Weise anzuzeigen.

Konsistenz mit PHPUnit

Aufgrund des Dateigriffs ist Paraunit in Bezug auf die von PHPUnit unterstützten Funktionen ziemlich eingeschränkt. Es ist ein hervorragendes Werkzeug für die Ausführung einer einfachen Struktur funktionaler Tests, aber zusätzlich zu einigen der bereits genannten Schwierigkeiten fehlt es an Unterstützung für einige nützliche PHPUnit-Funktionen. Zu diesen Beispielen zählen Testsuiten, Festlegen von Konfigurations- und Bootstrap-Dateien, Protokollieren von Ergebnissen und Ausführen bestimmter Testgruppen.

Viele der vorhandenen Werkzeuge folgen diesem Muster. Grep ein Verzeichnis der Testdateien und führe entweder die gesamte Datei in einem neuen Prozess oder jede Methode aus - niemals beide.


ParaTest bei Bat

Das Ziel von ParaTest ist es, parallele Tests für verschiedene Szenarien zu unterstützen. Ursprünglich geschaffen, um die Lücken in Paraunit zu füllen, wurde es zu einem robusten Befehlszeilentool für die parallele Ausführung von Testsuiten und Testmethoden. Dies macht ParaTest zu einem idealen Kandidaten für Langzeitprüfungen in verschiedenen Formen und Größen.

Wie ParaTest parallele Tests abwickelt

ParaTest weicht von der etablierten Norm ab, um mehr PHPUnit zu unterstützen, und ist ein wahrer Kandidat für parallele Tests.

Test laden

ParaTest lädt Tests auf ähnliche Weise wie PHPUnit. Es lädt alle Tests in ein angegebenes Verzeichnis, das mit der Endung endet * Test.php Suffix oder lädt Tests, die auf der Standard-XML-Konfigurationsdatei von PHPUnit basieren. Das Laden erfolgt über Reflektion, sodass es einfach unterstützt werden kann @Prüfung Methoden, Vererbung, Testreihen und individuelle Testmethoden. Reflection macht das Hinzufügen von Unterstützung für andere Anmerkungen zum Kinderspiel.

Da ParaTest mithilfe von Reflection Klassen und Methoden abrufen kann, können sowohl Testsuites als auch Testmethoden parallel ausgeführt werden, wodurch das Tool vielseitiger wird.

ParaTest hat einige Einschränkungen, aber fundierte Einschränkungen in der PHP-Community. Tests müssen dem PSR-0-Standard und dem Standard-Dateisuffix von folgen * Test.php ist nicht konfigurierbar wie in PHPUnit. Derzeit wird ein Zweig für die Unterstützung derselben Suffixkonfiguration in PHPUnit ausgeführt.

Ergebnisse anzeigen

ParaTest weicht auch vom Analysieren von STDOUT-Pipes ab. Anstatt Ausgabeströme zu analysieren, protokolliert ParaTest die Ergebnisse jedes PHPUnit-Prozesses im JUnit-Format und fasst die Ergebnisse dieser Protokolle zusammen. Es ist viel einfacher, Testergebnisse aus einem festgelegten Format zu lesen als aus einem Ausgabestrom.

        

Das Analysieren von JUnit-Protokollen hat einige kleinere Nachteile. Übersprungene und ignorierte Tests werden nicht in der unmittelbaren Rückmeldung gemeldet, sondern in den nach einem Testlauf angezeigten Gesamtwerten.

Konsistenz mit PHPUnit

Reflection ermöglicht ParaTest die Unterstützung weiterer PHPUnit-Konventionen. Die ParaTest-Konsole unterstützt standardmäßig mehr PHPUnit-Funktionen als jedes andere ähnliche Tool, z. B. die Möglichkeit, Gruppen auszuführen, Konfigurations- und Bootstrap-Dateien bereitzustellen und Ergebnisse im JUnit-Format zu protokollieren.


ParaTest-Beispiele

Mit ParaTest können Sie in verschiedenen Testszenarien Geschwindigkeit erzielen.

Funktionsprüfung mit Selen

ParaTest zeichnet sich durch Funktionstests aus. Es unterstützt a -f Schalter in der Konsole, um den Funktionsmodus zu aktivieren. Der Funktionsmodus weist ParaTest an, jede Testmethode in einem separaten Prozess anstelle der Standardmethode auszuführen, dh jede Testsuite in einem separaten Prozess auszuführen.

Es ist oft so, dass jede funktionale Testmethode viel Arbeit leistet, beispielsweise das Öffnen eines Browsers, das Navigieren auf der Seite und das Schließen des Browsers.

Das Beispielprojekt paratest-selenium demonstriert das Testen einer Backbone.js-ToDo-Anwendung mit Selenium und ParaTest. Jede Testmethode öffnet einen Browser und testet eine bestimmte Funktion:

 public function setUp () $ this-> setBrowserUrl ('http://backbonejs.org/examples/todos/'); $ this-> todos = new Todos ($ this-> preparSession ());  public function testTypingIntoFieldAndHittingEnterAddsTodo () $ this-> todos-> addTodo ("parallelize phpunit tests \ n"); $ this-> assertEquals (1, sizeof ($ this-> todos-> getItems ()));  public function testClickingTodoCheckboxMarksTodoDone () $ this-> todos-> addTodo ("Stellen Sie sicher, dass Sie todos abschließen können"); $ items = $ this-> todos-> getItems (); $ item = array_shift ($ items); $ this-> todos-> getItemCheckbox ($ item) -> click (); $ this-> assertEquals ('done', $ item-> attribute ('class'));  //… weitere Tests

Dieser Testfall kann eine kurze Sekunde dauern, wenn er seriell über Vanilla PHPUnit ausgeführt wird. Warum nicht mehrere Methoden gleichzeitig ausführen??

Umgang mit Rennbedingungen

Wie bei jedem Paralleltest müssen wir uns Szenarien vor Augen führen, in denen die Bedingungen des Rennens dargestellt werden - beispielsweise mehrere Prozesse, die versuchen, auf eine Datenbank zuzugreifen. Der Dev-Master-Zweig von ParaTest verfügt über eine sehr praktische Test-Token-Funktion, die von seinem Mitarbeiter Dimitris Baltas (dbaltas von Github) geschrieben wurde und die das Testen von Datenbanken für Datenbanken wesentlich vereinfacht.

Dimitris hat ein hilfreiches Beispiel enthalten, das diese Funktion in Github demonstriert. In Dimitris eigenen Worten:

TEST_TOKEN Es wird versucht, das Problem der gemeinsamen Ressourcen auf eine sehr einfache Weise zu lösen: Klonen Sie die Ressourcen, um sicherzustellen, dass keine gleichzeitigen Prozesse auf dieselbe Ressource zugreifen.

EIN TEST_TOKEN Die Umgebungsvariable wird für Tests bereitgestellt, die verbraucht werden, und wird wiederverwendet, wenn der Prozess abgeschlossen ist. Es kann verwendet werden, um Ihre Tests bedingt zu ändern, wie folgt:

 öffentliche Funktion setUp () parent :: setUp (); $ this -> _ filename = sprintf ('out% s.txt', getenv ('TEST_TOKEN')); 

ParaTest und Sauce Labs

Sauce Labs ist der Excalibur der Funktionsprüfung. Sauce Labs bietet einen Dienst, mit dem Sie Ihre Anwendungen auf verschiedenen Browsern und Plattformen einfach testen können. Wenn Sie sie noch nicht überprüft haben, empfehle ich Ihnen dringend, dies zu tun.

Das Testen mit Sauce kann an sich schon ein Tutorial sein, aber diese Assistenten haben bereits großartige Tutorials für die Verwendung von PHP und ParaTest zum Schreiben von Funktionstests unter Verwendung ihres Dienstes bereitgestellt.


Die Zukunft des ParaTests

ParaTest ist ein großartiges Werkzeug, um einige der Lücken von PHPUnit auszufüllen, aber letztendlich ist es nur ein Plug in the dam. Ein viel besseres Szenario wäre die native Unterstützung in PHPUnit!

In der Zwischenzeit wird ParaTest die Unterstützung für mehr Eigenverhalten von PHPUnit weiter erhöhen. Es bietet weiterhin Funktionen, die für das parallele Testen hilfreich sind - insbesondere im Funktions- und Integrationsbereich.

ParaTest hat viele großartige Dinge in den Werken, um die Transparenz zwischen PHPUnit und sich selbst zu verbessern, vor allem in den unterstützten Konfigurationsoptionen.

Die neueste stabile Version von ParaTest (v0.4.4) unterstützt Mac, Linux und Windows bequem, es gibt jedoch einige nützliche Pull-Anforderungen und -Funktionen Dev-Master das ist definitiv auf die Mac- und Linux-Massen ausgerichtet. Das wird also ein interessantes Gespräch sein, das voranschreitet.

Zusätzliche Lektüre und Ressourcen

Es gibt eine Handvoll Artikel und Ressourcen rund um das Internet, die ParaTest enthalten. Lesen Sie sie bei Interesse durch:

  • ParaTest auf Github
  • Paralleler PHPUnit-Beitrag von ParaTest-Mitwirkender und PHPUnit Selenium-Erweiterungsbetreuer Giorgio Sironi
  • Beiträge zu Paratest Ein hervorragender Artikel zu Giorgios experimentellem WrapperRunner für ParaTest
  • Giorgio's WrapperRunner-Quellcode
  • tripsta / paratest-probe. Ein Beispiel für die TEST_TOKEN-Funktion ihres Erstellers Dimitris Baltas
  • Brianium / Paratest-Selen. Ein Beispiel für die Verwendung von ParaTest zum Schreiben von Funktionstests