Ce package permet d'exécuter plus rapidement des tests unitaires liés au temps/IO (par exemple, appels de fonction de veille, requêtes de base de données, appels API, etc.) à l'aide de Swoole.
Le package counit permet d'exécuter simultanément plusieurs tests liés au temps/IO au sein d'un seul processus PHP à l'aide de Swoole. Counit est compatible avec PHPUnit , ce qui signifie :
Un cas de test typique de count ressemble à ceci :
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. ' );
}
}
Comparé à PHPUnit , counit pourrait rendre vos cas de test plus rapides. Voici une comparaison lors de l'exécution de la même suite de tests en utilisant PHPUnit et count pour un projet réel. Dans la suite de tests, de nombreux tests appellent la méthode DeminyCounitCounit::sleep() pour attendre que quelque chose se produise (par exemple, attendre l'expiration des données).
# de tests | Nombre d'assertions | Il est temps de terminer | |
---|---|---|---|
count (sans Swoole), ou PHPUnit | 44 | 1148 | 9 minutes et 18 secondes |
pays (avec Swoole activé) | 19 secondes |
Le package peut être installé à l'aide de Composer :
composer require deminy/counit --dev
Ou, dans votre fichier composer.json , assurez-vous que le package deminy/counit est inclus :
{
"require-dev" : {
"deminy/counit" : " ~0.2.0 "
}
}
Les dossiers ./tests/unit/global et ./tests/unit/case-by-case contiennent quelques exemples de tests, dans lesquels nous incluons les tests temporels suivants :
Pour exécuter les exemples de tests, veuillez démarrer les conteneurs Docker et installer d'abord les packages Composer :
docker-compose up -d
docker compose exec -ti swoole composer install -n
Il y a cinq conteneurs démarrés : un conteneur PHP, un conteneur Swoole, un conteneur Redis, un conteneur MySQL et un serveur Web. L'extension Swoole n'est pas installée sur le conteneur PHP, alors que le conteneur Swoole l'a installée et activée.
Comme dit précédemment, les cas de tests peuvent être écrits de la même manière que ceux de PHPUnit . Cependant, pour exécuter plus rapidement les tests liés au temps/IO avec countit , nous devons faire quelques ajustements lors de l'écriture de ces cas de test ; ces ajustements peuvent être effectués dans deux styles différents.
Dans ce style, chaque scénario de test s’exécute automatiquement dans une coroutine distincte.
Pour les cas de test écrits dans ce style, la seule modification à apporter à vos cas de test existants est d'utiliser la classe DeminyCounitTestCase au lieu de PHPUnitFrameworkTestCase comme classe de base.
Un cas de test typique du style global ressemble à ceci :
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. ' );
}
}
Lorsque les méthodes personnalisées setUpBeforeClass() et tearDownAfterClass() sont définies dans les cas de test, assurez-vous d'appeler leurs méthodes parent en conséquence dans ces méthodes personnalisées.
Ce style suppose qu'il n'y a pas d'assertions immédiates dans les cas de test, ni d'assertions avant un appel de fonction sleep() ou une opération d'E/S compatible avec la coroutine. Les cas de test comme ceux qui suivent fonctionnent toujours, mais ils déclencheront des messages d'avertissement une fois testés :
class GlobalTest extends Deminy Counit TestCase
{
public function testAssertionSuppression (): void
{
self :: assertTrue ( true , ' Trigger an immediate assertion. ' );
// ......
}
}
Nous pouvons réécrire cette classe de test en utilisant le style « au cas par cas » (abordé dans la section suivante) pour éliminer les messages d'avertissement.
Pour trouver plus de tests écrits dans ce style, veuillez consulter les tests sous le dossier ./tests/unit/global (suite de tests "global").
Dans ce style, vous apportez des modifications directement à un scénario de test pour le faire fonctionner de manière asynchrone.
Pour les cas de test écrits dans ce style, nous devons utiliser la classe DeminyCounitCounit en conséquence dans les cas de test où nous devons attendre l'exécution de PHP ou effectuer des opérations d'E/S. Généralement, les appels de méthode suivants seront utilisés :
Un cas de test typique du style au cas par cas ressemble à ceci :
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. ' );
});
}
}
Dans le cas où vous devez supprimer le message d'avertissement "Ce test n'a effectué aucune assertion" ou faire correspondre le nombre d'assertions, vous pouvez inclure un 2ème paramètre lors de la création de la nouvelle coroutine :
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.
);
}
}
Pour trouver plus de tests écrits dans ce style, veuillez consulter les tests dans le dossier ./tests/unit/case-by-case (suite de tests "au cas par cas").
Ici, nous exécuterons les tests dans différents environnements, avec ou sans Swoole.
#1
Exécutez les suites de tests en utilisant PHPUnit :
# 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
Exécutez les suites de tests en utilisant counit (sans 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
Exécutez les suites de tests en utilisant counit (avec l'extension Swoole activée) :
# 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
Les deux premiers ensembles de commandes prennent à peu près le même temps. Le dernier ensemble de commandes utilise counit et s'exécute dans le conteneur Swoole (où l'extension Swoole est activée) ; donc c'est plus rapide que les autres :
Style | # de tests | Nombre d'assertions | Il est temps de terminer | |
---|---|---|---|---|
count (sans Swoole), ou PHPUnit | mondial | 16 | 24 | 48 secondes |
au cas par cas | 48 secondes | |||
pays (avec Swoole activé) | mondial | 7 secondes | ||
au cas par cas | 7 secondes |
Étant donné que ce package permet d'exécuter plusieurs tests simultanément, nous ne devons pas utiliser les mêmes ressources dans différents tests ; sinon, des conditions de course pourraient survenir. Par exemple, si plusieurs tests utilisent la même clé Redis, certains d'entre eux peuvent échouer occasionnellement. Dans ce cas, nous devons utiliser différentes clés Redis dans différents cas de test. Les méthodes DeminyCounitHelper::getNewKey() et DeminyCounitHelper::getNewKeys() peuvent être utilisées pour générer des clés de test aléatoires et uniques.
Le package fonctionne mieux pour les tests qui utilisent l’appel de fonction sleep() ; Cela peut également aider à exécuter certains tests liés aux IO plus rapidement, avec des limitations applicables. Voici une liste des limitations de ce package :
Il existe des images deminy/counit prédéfinies pour exécuter les exemples de tests. Voici les commandes pour créer les images :
docker build -t deminy/counit:php-only -f ./dockerfiles/php/Dockerfile .
docker build -t deminy/counit:swoole-enabled -f ./dockerfiles/swoole/Dockerfile .
Ce package permet d'utiliser Swoole pour exécuter plusieurs tests liés au temps/IO sans multitraitement, ce qui signifie que tous les tests peuvent s'exécuter dans un seul processus PHP. Pour comprendre exactement comment cela fonctionne, je vous recommande de consulter cette conférence en ligne gratuite : Programmation CSP en PHP (et voici les diapositives).
Dans l'écosystème PHP, il existe d'autres options pour exécuter des tests unitaires en parallèle, la plupart finissent par utiliser le multitraitement :
Licence MIT.