PHP 测试:高级单元测试、集成测试与端到端测试
在现代 PHP Web 开发中,代码质量已不再是可选项,而是项目生存的必要条件。每个开发者都熟悉基本的单元测试,但一旦项目超越“Hello World”的范畴,就需要更复杂的方法:集成测试、组件交互验证以及用户场景的端到端测试 (E2E)。
本文面向 Middle 及以上级别的 PHP 开发者,他们已熟悉 PHPUnit 并希望深化知识。我们将探讨高级技术:外部依赖测试、测试容器使用、基础设施隔离以及构建完整的测试策略。
1. 高级单元测试:模拟对象与桩代码
在高级别上,单元测试不再仅仅是检查数学函数。它们测试与外部世界隔离的业务逻辑。关键工具是 Mock 和 Stub。
Stub 返回预定义数据。Mock 检查方法是如何被调用的(调用次数、参数顺序)。
考虑一个示例:一个向支付网关发送请求的支付服务。
<?phpclass PaymentService { public function __construct( private PaymentGatewayInterface $gateway, private LoggerInterface $logger ) {}
public function processPayment(Order $order): bool { $result = $this->gateway->charge( $order->getAmount(), $order->getCurrency() );
if ($result->isSuccess()) { $this->logger->info('Payment processed', ['order_id' => $order->getId()]); return true; }
$this->logger->warning('Payment failed', ['error' => $result->getError()]); return false; }}
// 使用 PHPUnit 进行测试class PaymentServiceTest extends TestCase { public function testSuccessfulPaymentLogsInfo(): void { // 为网关创建 Stub $gatewayStub = $this->createStub(PaymentGatewayInterface::class); $gatewayStub->method('charge') ->willReturn(new PaymentResult(true));
// 为日志记录器创建 Mock(检查调用) $loggerMock = $this->createMock(LoggerInterface::class); $loggerMock->expects($this->once()) ->method('info') ->with('Payment processed', $this->arrayHasKey('order_id'));
$service = new PaymentService($gatewayStub, $loggerMock); $result = $service->processPayment(new Order(100, 'USD'));
$this->assertTrue($result); }}高级技术:使用 willReturnCallback() 实现动态响应,使用 withConsecutive() 检查调用顺序。
2. 集成测试:数据库与外部 API
集成测试检查您的代码如何与真实组件交互:数据库、文件系统、缓存。这里不能依赖模拟对象——需要真实的基础设施。
问题:测试变得缓慢且脆弱。解决方案:使用 测试容器 (Testcontainers) 或内置 SQLite 数据库。
使用 Doctrine 的用户仓库集成测试示例:
<?phpclass UserRepositoryIntegrationTest extends KernelTestCase { private EntityManagerInterface $entityManager; private UserRepository $repository;
protected function setUp(): void { self::bootKernel(); $this->entityManager = self::$kernel->getContainer() ->get('doctrine