Этот пакет помогает быстрее выполнять модульные тесты, связанные со временем и вводом-выводом (например, вызовы функций сна, запросы к базе данных, вызовы API и т. д.), используя Swoole.
Пакет counit позволяет одновременно запускать несколько тестов, связанных со временем и вводом-выводом, в рамках одного процесса PHP с использованием Swoole. 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 секунд |
единица (с включенным Swoole) | 19 секунд |
Пакет можно установить с помощью Composer :
composer require deminy/counit --dev
Или в файле композитора.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 и веб-сервер. В контейнере PHP не установлено расширение Swoole, а в контейнере Swoole оно установлено и включено.
Как было сказано ранее, тестовые примеры могут быть написаны так же, как и для PHPUnit . Однако, чтобы быстрее выполнять тесты, связанные со временем/IO, с помощью counit , нам нужно внести некоторые изменения при написании этих тестовых случаев; эти корректировки могут быть выполнены в двух разных стилях.
В этом стиле каждый тестовый пример автоматически запускается в отдельной сопрограмме.
Для тестовых случаев, написанных в этом стиле, единственное изменение, которое необходимо внести в существующие тестовые сценарии, — это использовать класс 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() или операцией ввода-вывода, совместимой с сопрограммой. Тестовые случаи, подобные следующим, по-прежнему работают, но при тестировании они выдают некоторые предупреждающие сообщения:
class GlobalTest extends Deminy Counit TestCase
{
public function testAssertionSuppression (): void
{
self :: assertTrue ( true , ' Trigger an immediate assertion. ' );
// ......
}
}
Мы можем переписать этот тестовый класс, используя стиль «индивидуально» (обсуждаемый в следующем разделе), чтобы исключить предупреждающие сообщения.
Чтобы найти больше тестов, написанных в этом стиле, проверьте тесты в папке ./tests/unit/global (набор тестов «глобальный»).
В этом стиле вы вносите изменения непосредственно в тестовый пример, чтобы он работал асинхронно.
Для тестовых случаев, написанных в этом стиле, нам необходимо использовать класс DeminyCounitCounit соответственно в тестовых случаях, когда нам нужно дождаться выполнения PHP или выполнить операции ввода-вывода. Обычно используются следующие вызовы методов:
Типичный тестовый пример индивидуального стиля выглядит следующим образом:
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 (набор тестов «для каждого случая»).
Здесь мы проведем тесты в разных средах, со 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 секунд | |||
единица (с включенным Swoole) | глобальный | 7 секунд | ||
в каждом конкретном случае | 7 секунд |
Поскольку этот пакет позволяет запускать несколько тестов одновременно, нам не следует использовать одни и те же ресурсы в разных тестах; в противном случае могут возникнуть условия гонок. Например, если несколько тестов используют один и тот же ключ Redis, некоторые из них могут иногда завершаться неудачно. В этом случае нам следует использовать разные ключи Redis в разных тестовых случаях. Методы DeminyCounitHelper::getNewKey() и DeminyCounitHelper::getNewKeys() могут использоваться для генерации случайных и уникальных тестовых ключей.
Пакет лучше всего работает для тестов, в которых используется вызов функции Sleep() ; Это также может помочь ускорить выполнение некоторых тестов, связанных с вводом-выводом, с учетом ограничений. Вот список ограничений этого пакета:
Существуют готовые образы 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 для запуска нескольких тестов, связанных со временем и вводом-выводом, без многопроцессорности, что означает, что все тесты могут выполняться в рамках одного процесса PHP. Чтобы понять, как именно это работает, я бы порекомендовал просмотреть этот бесплатный онлайн-доклад: Программирование CSP на PHP (вот слайды).
В экосистеме PHP есть и другие варианты параллельного запуска модульных тестов, большинство из которых в конечном итоге используют многопроцессорность:
Лицензия МТИ.