该包有助于使用 Swoole 更快地运行时间/IO 相关的单元测试(例如,睡眠函数调用、数据库查询、API 调用等)。
counit包允许使用 Swoole 在单个 PHP 进程中同时运行多个时间/IO 相关测试。 Counit与PHPUnit兼容,这意味着:
一个典型的counit测试用例如下所示:
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. ' );
}
}
与PHPUnit相比, counit可以使您的测试用例更快。以下是在实际项目中使用PHPUnit和counit运行相同测试套件时的比较。在测试套件中,许多测试调用方法DeminyCounitCounit::sleep()来等待某些事情发生(例如,等待数据过期)。
测试次数 | 断言数量 | 完成时间 | |
---|---|---|---|
counit(不含 Swoole)或 PHPUnit | 44 | 1148 | 9分18秒 |
counit(启用 Swoole) | 19秒 |
可以使用Composer安装该软件包:
composer require deminy/counit --dev
或者,在您的composer.json文件中,确保包含deminy/counit包:
{
"require-dev" : {
"deminy/counit" : " ~0.2.0 "
}
}
文件夹 ./tests/unit/global 和 ./tests/unit/case-by-case 包含一些示例测试,其中包括以下与时间相关的测试:
要运行示例测试,请首先启动 Docker 容器并安装 Composer 软件包:
docker-compose up -d
docker compose exec -ti swoole composer install -n
启动了五个容器:PHP容器、Swoole容器、Redis容器、MySQL容器和Web服务器。 PHP 容器没有安装 Swoole 扩展,而 Swoole 容器已安装并启用它。
如前所述,测试用例的编写方式与PHPUnit的编写方式相同。然而,为了使用counit更快地运行时间/IO 相关测试,我们需要在编写这些测试用例时进行一些调整;这些调整可以通过两种不同的方式进行。
在这种风格中,每个测试用例自动在单独的协程中运行。
对于以这种风格编写的测试用例,对现有测试用例进行的唯一更改是使用类DeminyCounitTestCase而不是PHPUnitFrameworkTestCase作为基类。
全局样式的典型测试用例如下所示:
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. ' );
}
}
当测试用例中定义了自定义方法setUpBeforeClass()和tearDownAfterClass()时,请确保在这些自定义方法中相应地调用其父方法。
这种风格假设测试用例中没有立即断言,也没有在 sleep() 函数调用或协程友好的 IO 操作之前进行断言。像下面这样的测试用例仍然有效,但是它们在测试时会触发一些警告消息:
class GlobalTest extends Deminy Counit TestCase
{
public function testAssertionSuppression (): void
{
self :: assertTrue ( true , ' Trigger an immediate assertion. ' );
// ......
}
}
我们可以使用“case by case”风格(在下一节中讨论)重写这个测试类来消除警告消息。
要查找更多以这种风格编写的测试,请检查文件夹 ./tests/unit/global 下的测试(测试套件“global”)。
在这种风格中,您可以直接对测试用例进行更改以使其异步工作。
对于这种风格编写的测试用例,在需要等待PHP执行或执行IO操作的测试用例中,我们需要相应地使用DeminyCounitCounit类。通常,将使用以下方法调用:
案例分析风格的典型测试用例如下所示:
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. ' );
});
}
}
如果您需要抑制警告消息“此测试未执行任何断言”或使断言数量匹配,您可以在创建新协程时包含第二个参数:
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.
);
}
}
要查找更多以这种风格编写的测试,请检查文件夹 ./tests/unit/case-by-case 下的测试(测试套件“case-by-case”)。
这里我们将在不同的环境下运行测试,无论有没有Swoole。
#1
使用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
使用counit运行测试套件(不使用 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
使用counit运行测试套件(启用扩展 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
前两组命令需要大约相同的时间才能完成。最后一组命令使用counit并在 Swoole 容器中运行(其中启用了 Swoole 扩展);因此它比其他的更快:
风格 | 测试次数 | 断言数量 | 完成时间 | |
---|---|---|---|---|
counit(不含 Swoole)或 PHPUnit | 全球的 | 16 | 24 | 48秒 |
逐案 | 48秒 | |||
counit(启用 Swoole) | 全球的 | 7秒 | ||
逐案 | 7秒 |
由于该包允许同时运行多个测试,因此我们不应该在不同的测试中使用相同的资源;否则,可能会出现赛车状况。例如,如果多个测试使用相同的 Redis 密钥,其中一些测试可能偶尔会失败。在这种情况下,我们应该在不同的测试用例中使用不同的Redis密钥。方法DeminyCounitHelper::getNewKey()和DeminyCounitHelper::getNewKeys()可用于生成随机且唯一的测试密钥。
该包最适合使用函数调用sleep()的测试;它还可以帮助更快地运行一些 IO 相关的测试,但有限制。以下是该软件包的限制列表:
有预先构建的图像 deminy/counit 用于运行示例测试。以下是构建图像的命令:
docker build -t deminy/counit:php-only -f ./dockerfiles/php/Dockerfile .
docker build -t deminy/counit:swoole-enabled -f ./dockerfiles/swoole/Dockerfile .
该包允许使用 Swoole 运行多个时间/IO 相关测试,而无需多重处理,这意味着所有测试都可以在单个 PHP 进程中运行。要了解它到底是如何工作的,我建议您查看这个免费的在线讲座:CSP PHP 编程(这里是幻灯片)。
在 PHP 生态系统中,还有其他并行运行单元测试的选项,大多数最终都使用多处理:
麻省理工学院许可证。