Dieses Paket hilft dabei, zeit-/IO-bezogene Komponententests (z. B. Aufrufe von Schlaffunktionen, Datenbankabfragen, API-Aufrufe usw.) mit Swoole schneller auszuführen.
Das Paket counit ermöglicht die gleichzeitige Ausführung mehrerer zeit-/IO-bezogener Tests innerhalb eines einzigen PHP-Prozesses mit Swoole. Counit ist mit PHPUnit kompatibel, was bedeutet:
Ein typischer Testfall von counit sieht folgendermaßen aus:
use Deminy Counit TestCase ; // Here is the only change made for counit, comparing to test cases for PHPUnit.
class SleepTest extends TestCase
{
public function testSleep (): void
{
$ startTime = time ();
sleep ( 3 );
$ endTime = time ();
self :: assertEqualsWithDelta ( 3 , ( $ endTime - $ startTime ), 1 , ' The sleep() function call takes about 3 seconds to finish. ' );
}
}
Im Vergleich zu PHPUnit könnte counit Ihre Testfälle schneller machen. Hier ist ein Vergleich beim Ausführen derselben Testsuite mit PHPUnit und counit für ein echtes Projekt. In der Testsuite rufen viele Tests die Methode DeminyCounitCounit::sleep() auf, um darauf zu warten, dass etwas passiert (z. B. darauf, dass Daten ablaufen).
Anzahl der Tests | Anzahl der Behauptungen | Zeit zum Abschluss | |
---|---|---|---|
counit (ohne Swoole) oder PHPUnit | 44 | 1148 | 9 Minuten und 18 Sekunden |
counit (mit aktiviertem Swoole) | 19 Sekunden |
Das Paket kann mit Composer installiert werden:
composer require deminy/counit --dev
Oder stellen Sie sicher, dass in Ihrer Datei „composer.json“ das Paket deminy/counit enthalten ist:
{
"require-dev" : {
"deminy/counit" : " ~0.2.0 "
}
}
Die Ordner ./tests/unit/global und ./tests/unit/case-by-case enthalten einige Beispieltests, in denen die folgenden zeitbezogenen Tests enthalten sind:
Um die Beispieltests auszuführen, starten Sie bitte zunächst die Docker-Container und installieren Sie Composer-Pakete:
docker-compose up -d
docker compose exec -ti swoole composer install -n
Es werden fünf Container gestartet: ein PHP-Container, ein Swoole-Container, ein Redis-Container, ein MySQL-Container und ein Webserver. Im PHP-Container ist die Swoole-Erweiterung nicht installiert, während sie im Swoole-Container installiert und aktiviert ist.
Wie bereits erwähnt, können Testfälle auf die gleiche Weise geschrieben werden wie die für PHPUnit . Um jedoch zeit-/IO-bezogene Tests mit counit schneller auszuführen, müssen wir beim Schreiben dieser Testfälle einige Anpassungen vornehmen; Diese Anpassungen können in zwei verschiedenen Stilen vorgenommen werden.
Bei diesem Stil wird jeder Testfall automatisch in einer separaten Coroutine ausgeführt.
Bei in diesem Stil geschriebenen Testfällen besteht die einzige Änderung, die Sie an Ihren vorhandenen Testfällen vornehmen müssen, darin, die Klasse DeminyCounitTestCase anstelle von PHPUnitFrameworkTestCase als Basisklasse zu verwenden.
Ein typischer Testfall des globalen Stils sieht so aus:
use Deminy Counit TestCase ; // Here is the only change made for counit, comparing to test cases for PHPUnit.
class SleepTest extends TestCase
{
public function testSleep (): void
{
$ startTime = time ();
sleep ( 3 );
$ endTime = time ();
self :: assertEqualsWithDelta ( 3 , ( $ endTime - $ startTime ), 1 , ' The sleep() function call takes about 3 seconds to finish. ' );
}
}
Wenn in den Testfällen die benutzerdefinierten Methoden setUpBeforeClass() und tearDownAfterClass() definiert sind, stellen Sie bitte sicher, dass ihre übergeordneten Methoden in diesen benutzerdefinierten Methoden entsprechend aufgerufen werden.
Bei diesem Stil wird davon ausgegangen, dass es in Testfällen weder unmittelbare Zusicherungen noch Zusicherungen vor einem Aufruf der Funktion „sleep()“ oder einer Coroutine-freundlichen E/A-Operation gibt. Testfälle wie die folgenden funktionieren weiterhin, lösen jedoch beim Testen einige Warnmeldungen aus:
class GlobalTest extends Deminy Counit TestCase
{
public function testAssertionSuppression (): void
{
self :: assertTrue ( true , ' Trigger an immediate assertion. ' );
// ......
}
}
Wir können diese Testklasse mithilfe des „Fall-für-Fall“-Stils (siehe nächster Abschnitt) umschreiben, um die Warnmeldungen zu eliminieren.
Um weitere in diesem Stil geschriebene Tests zu finden, überprüfen Sie bitte die Tests im Ordner ./tests/unit/global (Testsuite „global“).
Bei diesem Stil nehmen Sie Änderungen direkt an einem Testfall vor, damit dieser asynchron funktioniert.
Für in diesem Stil geschriebene Testfälle müssen wir die Klasse DeminyCounitCounit entsprechend in den Testfällen verwenden, in denen wir auf die PHP-Ausführung warten oder E/A-Vorgänge ausführen müssen. Typischerweise werden folgende Methodenaufrufe verwendet:
Ein typischer Testfall des Case-by-Case-Stils sieht so aus:
use Deminy Counit Counit ;
use PHPUnit Framework TestCase ;
class SleepTest extends TestCase
{
public function testSleep (): void
{
Counit:: create ( function () { // To create a new coroutine manually to run the test case.
$ startTime = time ();
Counit:: sleep ( 3 ); // Call this method instead of PHP function sleep().
$ endTime = time ();
self :: assertEqualsWithDelta ( 3 , ( $ endTime - $ startTime ), 1 , ' The sleep() function call takes about 3 seconds to finish. ' );
});
}
}
Falls Sie die Warnmeldung „Dieser Test hat keine Behauptungen durchgeführt“ unterdrücken oder dafür sorgen müssen, dass die Anzahl der Behauptungen übereinstimmt, können Sie beim Erstellen der neuen Coroutine einen zweiten Parameter einschließen:
use Deminy Counit Counit ;
use PHPUnit Framework TestCase ;
class SleepTest extends TestCase
{
public function testSleep (): void
{
Counit:: create ( // To create a new coroutine manually to run the test case.
function () {
$ startTime = time ();
Counit:: sleep ( 3 ); // Call this method instead of PHP function sleep().
$ endTime = time ();
self :: assertEqualsWithDelta ( 3 , ( $ endTime - $ startTime ), 1 , ' The sleep() function call takes about 3 seconds to finish. ' );
},
1 // Optional. To suppress warning message "This test did not perform any assertions", and to make the counters match.
);
}
}
Um weitere in diesem Stil geschriebene Tests zu finden, überprüfen Sie bitte die Tests im Ordner ./tests/unit/case-by-case (Testsuite „case-by-case“).
Hier werden wir die Tests in verschiedenen Umgebungen durchführen, mit oder ohne Swoole.
#1
Führen Sie die Testsuiten mit PHPUnit aus:
# To run test suite "global":
docker compose exec -ti php ./vendor/bin/phpunit --testsuite global
# or,
docker compose exec -ti swoole ./vendor/bin/phpunit --testsuite global
# To run test suite "case-by-case":
docker compose exec -ti php ./vendor/bin/phpunit --testsuite case-by-case
# or,
docker compose exec -ti swoole ./vendor/bin/phpunit --testsuite case-by-case
#2
Führen Sie die Testsuiten mit counit aus (ohne Swoole):
# To run test suite "global":
docker compose exec -ti php ./counit --testsuite global
# To run test suite "case-by-case":
docker compose exec -ti php ./counit --testsuite case-by-case
#3
Führen Sie die Testsuiten mit counit aus (mit aktivierter Erweiterung Swoole):
# To run test suite "global":
docker compose exec -ti swoole ./counit --testsuite global
# To run test suite "case-by-case":
docker compose exec -ti swoole ./counit --testsuite case-by-case
Die Ausführung der ersten beiden Befehlssätze dauert ungefähr gleich lange. Der letzte Befehlssatz verwendet counit und wird im Swoole-Container ausgeführt (wo die Swoole-Erweiterung aktiviert ist). daher ist es schneller als die anderen:
Stil | Anzahl der Tests | Anzahl der Behauptungen | Zeit zum Abschluss | |
---|---|---|---|---|
counit (ohne Swoole) oder PHPUnit | global | 16 | 24 | 48 Sekunden |
Fall für Fall | 48 Sekunden | |||
counit (mit aktiviertem Swoole) | global | 7 Sekunden | ||
Fall für Fall | 7 Sekunden |
Da dieses Paket die gleichzeitige Ausführung mehrerer Tests ermöglicht, sollten wir nicht dieselben Ressourcen in verschiedenen Tests verwenden; Andernfalls könnte es zu Rennbedingungen kommen. Wenn beispielsweise mehrere Tests denselben Redis-Schlüssel verwenden, können einige von ihnen gelegentlich fehlschlagen. In diesem Fall sollten wir in verschiedenen Testfällen unterschiedliche Redis-Schlüssel verwenden. Mit den Methoden DeminyCounitHelper::getNewKey() und DeminyCounitHelper::getNewKeys() können zufällige und eindeutige Testschlüssel generiert werden.
Das Paket eignet sich am besten für Tests, bei denen der Funktionsaufruf „sleep()“ verwendet wird. Es kann auch hilfreich sein, einige E/A-bezogene Tests schneller auszuführen, allerdings mit Einschränkungen. Hier ist eine Liste der Einschränkungen dieses Pakets:
Es gibt vorgefertigte Images deminy/counit zum Ausführen der Beispieltests. Hier sind die Befehle zum Erstellen der Bilder:
docker build -t deminy/counit:php-only -f ./dockerfiles/php/Dockerfile .
docker build -t deminy/counit:swoole-enabled -f ./dockerfiles/swoole/Dockerfile .
Dieses Paket ermöglicht die Verwendung von Swoole zur Ausführung mehrerer zeit-/IO-bezogener Tests ohne Multiprocessing, was bedeutet, dass alle Tests innerhalb eines einzigen PHP-Prozesses ausgeführt werden können. Um zu verstehen, wie es genau funktioniert, empfehle ich Ihnen, diesen kostenlosen Online-Vortrag zu lesen: CSP-Programmierung in PHP (und hier sind die Folien).
Im PHP-Ökosystem gibt es andere Möglichkeiten, Unit-Tests parallel auszuführen, die meisten nutzen letztendlich Multiprocessing:
MIT-Lizenz.