专为 Workerman 应用设计的 PHPUnit 测试框架,解决异步、事件驱动应用的测试难题。
- 事件循环模拟 - 提供完全可控的事件循环和时间控制
- 异步操作测试 - 支持定时器、延迟操作和回调测试
- 连接模拟 - 完整的 TCP 连接生命周期模拟
- Worker 管理 - 创建、启动和管理 Worker 实例
- 专用断言 - 针对 Workerman 特性的断言方法
- 异步操作测试
- 事件循环时间控制
- 网络连接模拟
- 多进程 Worker 测试
- 定时器功能验证
- PHP 8.1 或更高版本
- ext-sockets 扩展
- workerman/workerman ^5.1
- phpunit/phpunit ^10.0 (开发依赖)
composer require --dev tourze/phpunit-workerman<?php
use Tourze\PHPUnitWorkerman\Core\WorkermanTestCase;
use Workerman\Worker;
class MyWorkerTest extends WorkermanTestCase
{
public function testBasicWorker(): void
{
// 创建测试 Worker
$worker = $this->createWorker('tcp://127.0.0.1:8080');
$messageReceived = false;
$worker->onMessage = function ($connection, $data) use (&$messageReceived) {
$messageReceived = true;
$connection->send('echo: ' . $data);
};
// 启动 Worker
$this->startWorker($worker);
// 模拟发送数据
$this->sendDataToWorker($worker, 'hello');
// 验证结果
$this->assertTrue($messageReceived);
}
}<?php
use Tourze\PHPUnitWorkerman\Core\AsyncTestCase;
use Workerman\Timer;
class TimerTest extends AsyncTestCase
{
public function testTimer(): void
{
$executed = false;
// 创建定时器
Timer::add(0.5, function () use (&$executed) {
$executed = true;
}, [], false);
// 快进时间
$this->advanceTime(0.6);
// 验证定时器执行
$this->assertTrue($executed);
}
public function testRepeatingTimer(): void
{
$count = 0;
Timer::add(0.1, function () use (&$count) {
$count++;
}, [], true);
// 快进 1 秒,应该执行 10 次
$this->advanceTime(1.0);
$this->assertEquals(10, $count);
}
}<?php
use Tourze\PHPUnitWorkerman\Core\WorkermanTestCase;
use Workerman\Connection\TcpConnection;
class ConnectionTest extends WorkermanTestCase
{
public function testConnection(): void
{
$worker = $this->createWorker();
$events = [];
$worker->onConnect = function ($connection) use (&$events) {
$events[] = 'connected';
};
$worker->onMessage = function ($connection, $data) use (&$events) {
$events[] = "message: $data";
};
$this->startWorker($worker);
// 模拟连接
$connection = $this->createMockConnection($worker);
$worker->onConnect($connection);
// 模拟消息
$this->sendDataToWorker($worker, 'test');
$this->assertEquals(['connected', 'message: test'], $events);
}
}用于一般的 Workerman 应用测试,提供 Worker 管理和连接模拟。
abstract class WorkermanTestCase extends TestCase
{
// 创建测试 Worker
protected function createWorker(string $socketName = ''): Worker;
// 启动 Worker
protected function startWorker(Worker $worker): void;
// 模拟发送数据到 Worker
protected function sendDataToWorker(Worker $worker, string $data): void;
// 时间快进
protected function fastForward(float $seconds): void;
// 等待条件满足
protected function waitFor(callable $condition, float $timeout = 5.0): void;
}专门用于异步操作测试
abstract class AsyncTestCase extends TestCase
{
// 时间推进
protected function advanceTime(float $seconds): void;
// 等待直到条件满足
protected function waitUntil(callable $condition, float $timeout = 5.0): void;
// 运行异步操作
protected function runAsync(callable $asyncOperation, float $timeout = 5.0);
// 并行运行多个异步操作
protected function runAsyncParallel(array $operations, float $timeout = 5.0): array;
}模拟事件循环
class MockEventLoop implements EventInterface
{
// 时间快进
public function fastForward(float $seconds): void;
// 触发事件
public function triggerRead($stream): void;
public function triggerWrite($stream): void;
public function triggerSignal(int $signal): void;
// 状态查询
public function getCurrentTime(): float;
public function getTimers(): array;
public function hasReadEvents(): bool;
}时间控制相关功能
trait TimeControl
{
protected function fastForward(float $seconds): void;
protected function fastForwardToNextTimer(): bool;
protected function executeAllPendingTimers(float $maxTime = 3600.0): int;
protected function setCurrentTime(float $time): void;
protected function getCurrentTime(): float;
}异步操作断言
trait AsyncAssertions
{
protected function assertAsyncCallbackCalled(callable $trigger, float $timeout = 5.0): void;
protected function assertAsyncCallbackCalledTimes(int $expectedCount, callable $trigger): void;
protected function assertAsyncResult($expectedValue, callable $trigger): void;
protected function waitForCondition(callable $condition, float $timeout = 5.0): void;
}连接模拟功能
trait ConnectionMocking
{
protected function createMockConnection(?Worker $worker = null, string $remoteAddress = '127.0.0.1:12345'): TcpConnection;
protected function mockConnectionSend(TcpConnection $connection, string $data): bool;
protected function mockConnectionReceive(TcpConnection $connection, string $data): void;
protected function mockConnectionClose(TcpConnection $connection): void;
protected function assertConnectionStatus(TcpConnection $connection, int $expectedStatus): void;
}public function testComplexAsyncFlow(): void
{
$results = [];
// 模拟复杂异步操作链
$this->runAsync(function ($resolve, $reject) use (&$results) {
Timer::add(0.1, function () use (&$results, $resolve) {
$results[] = 'step1';
Timer::add(0.2, function () use (&$results, $resolve) {
$results[] = 'step2';
Timer::add(0.3, function () use (&$results, $resolve) {
$results[] = 'step3';
$resolve($results);
}, [], false);
}, [], false);
}, [], false);
});
$this->assertEquals(['step1', 'step2', 'step3'], $results);
}public function testHttpProtocol(): void
{
$worker = $this->createWorker('tcp://127.0.0.1:8080');
$worker->protocol = \Workerman\Protocols\Http::class;
$response = null;
$worker->onMessage = function ($connection, $request) use (&$response) {
$response = $request;
$connection->send("HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nHello World");
};
$this->startWorker($worker);
// 发送 HTTP 请求
$httpRequest = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
$this->sendDataToWorker($worker, $httpRequest);
$this->assertNotNull($response);
$this->assertStringContainsString('GET /', (string)$response);
}public function testHighConcurrency(): void
{
$worker = $this->createWorker();
$connectionCount = 0;
$worker->onConnect = function () use (&$connectionCount) {
$connectionCount++;
};
// 创建1000个并发连接
$connections = $this->createMassConnections($worker, 1000);
$this->assertEquals(1000, $connectionCount);
$this->assertCount(1000, $connections);
}public function testErrorHandling(): void
{
$worker = $this->createWorker();
$errorHandled = false;
$worker->onError = function ($connection, $code, $msg) use (&$errorHandled) {
$errorHandled = true;
$this->assertEquals(500, $code);
$this->assertEquals('测试错误', $msg);
};
$worker->onMessage = function ($connection, $data) {
if ($data === 'error') {
throw new \RuntimeException('测试错误');
}
};
$this->startWorker($worker);
// 触发错误
$this->sendDataToWorker($worker, 'error');
$this->assertTrue($errorHandled);
}正确设置和清理测试环境
class MyTest extends WorkermanTestCase
{
protected function setUp(): void
{
parent::setUp(); // 必须调用父类方法
}
protected function tearDown(): void
{
parent::tearDown(); // 必须调用父类方法
}
}为异步操作设置合理的超时时间
$this->waitFor(fn() => $condition, 5.0); // 5秒超时
$this->runAsync($operation, 10.0); // 10秒超时使用时间快进而不是真实等待
// 推荐:瞬间完成
$this->fastForward(2.0);
// 不推荐:真实等待
sleep(2);及时清理连接和资源
$connection = $this->createMockConnection($worker);
// ... 测试逻辑
$this->mockConnectionClose($connection); // 测试结束前清理Q: 定时器测试失败怎么办?
A: 使用 fastForward() 方法而不是真实等待
Q: 连接状态异常?
A: 确保使用 mockConnectionClose() 等方法正确管理连接生命周期
Q: 测试运行超时? A: 检查异步操作是否正确完成,确保没有无限循环
Q: 测试环境初始化失败?
A: 确保测试类正确继承并调用 parent::setUp() 和 parent::tearDown()
如果测试较慢,可以尝试:
// 减少 Worker 数量
$this->createWorker(); // 而不是多个
// 调试定时器状态
$timerCount = $this->getPendingTimerCount();
$timers = $this->eventLoop->getTimers();欢迎提交 Issue 和 Pull Request 来改进这个测试框架。
git clone <repository>
cd phpunit-workerman
composer install
vendor/bin/phpunit遵循 PSR-12 编码标准
MIT 许可证
这个测试框架让您能够轻松测试 Workerman 应用中的异步操作、事件处理和网络通信功能。