PHP与Docker集成测试:从入门到精通
现代PHP开发离不开高质量的测试。如果单元测试检查的是独立的代码片段,那么集成测试则确保系统的所有组件(数据库、Redis、外部API和文件系统)能够协同工作。Docker使这一过程可重现、快速且独立于开发环境。本文将从头讲解如何使用Docker配置PHP应用程序的集成测试。
为什么Docker是集成测试的理想工具?
传统集成测试面临的问题:
- 环境依赖——每个开发者拥有不同版本的MySQL、PHP、Redis。
- 数据污染——测试会在真实数据库中留下垃圾数据。
- 并行运行困难——测试之间相互冲突。
Docker解决了这些问题:
- 每个测试在具有已知依赖版本的纯净容器中运行。
- 容器相互隔离,运行后自动删除。
- Docker Compose可一键启动整个基础设施。
第一步:项目结构与Docker Compose
从一个典型的Symfony PHP应用程序开始。创建用于测试环境的docker-compose.test.yml文件:
version: '3.8'services: php: build: context: . dockerfile: Dockerfile.test environment: DATABASE_URL: "mysql://test:test@mysql:3306/test_db?serverVersion=8.0" REDIS_URL: "redis://redis:6379" depends_on: - mysql - redis
mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: test_db MYSQL_USER: test MYSQL_PASSWORD: test tmpfs: /var/lib/mysql # 数据存储在内存中——速度更快
redis: image: redis:7-alpine tmpfs: /dataDockerfile.test文件:
FROM php:8.2-cli
RUN docker-php-ext-install pdo_mysqlRUN pecl install redis && docker-php-ext-enable redis
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /appCOPY . .
RUN composer install --no-interaction --prefer-dist
CMD ["vendor/bin/phpunit", "--configuration", "phpunit.xml.dist"]注意tmpfs——这是将临时文件系统挂载到内存中。数据库和Redis的运行速度将比磁盘快得多,这对快速测试至关重要。
第二步:为集成测试配置PHPUnit
创建单独的配置文件phpunit.integration.xml.dist:
<?xml version="1.0" encoding="UTF-8"?><phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" bootstrap="tests/bootstrap.php" colors="true"> <testsuites> <testsuite name="Integration"> <directory>tests/Integration</directory> </testsuite> </testsuites> <php> <env name="APP_ENV" value="test"/> <env name="KERNEL_CLASS" value="App\\Kernel"/> </php></phpunit>用于初始化测试环境的tests/bootstrap.php文件:
<?php
require dirname(__DIR__) . '/vendor/autoload.php';
// 等待MySQL和Redis就绪$maxAttempts = 30;$connected = false;
for ($i = 0; $i < $maxAttempts; $i++) { try { $pdo = new PDO( $_ENV['DATABASE_URL'], null, null, [PDO::ATTR_TIMEOUT => 1] ); $connected = true; break; } catch (PDOException $e) { sleep(1); }}\