該套件有助於使用 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 生態系統中,還有其他並行運行單元測試的選項,大多數最終都使用多處理:
麻省理工學院許可證。