本文目录导读:

我来详细介绍PHP项目集成测试自动化流程的搭建方案:
整体架构设计
graph LR
A[代码提交] --> B[代码检查]
B --> C[单元测试]
C --> D[集成测试]
D --> E[代码质量]
E --> F[部署]
基础设施搭建
测试环境容器化
# Dockerfile.test
FROM php:8.2-fpm
# 安装依赖
RUN apt-get update && apt-get install -y \
git \
unzip \
libpq-dev \
libmysqlclient-dev \
&& docker-php-ext-install pdo pdo_mysql pdo_pgsql
# 安装Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# 安装测试工具
RUN pecl install xdebug && docker-php-ext-enable xdebug
WORKDIR /var/www/html
Docker Compose测试环境
# docker-compose.test.yml
version: '3.8'
services:
test-runner:
build:
context: .
dockerfile: Dockerfile.test
environment:
- APP_ENV=test
- DB_HOST=test-db
- DB_DATABASE=test_db
- DB_USERNAME=test_user
- DB_PASSWORD=test_pass
- REDIS_HOST=test-redis
volumes:
- .:/var/www/html
depends_on:
- test-db
- test-redis
test-db:
image: mysql:8.0
environment:
MYSQL_DATABASE: test_db
MYSQL_USER: test_user
MYSQL_PASSWORD: test_pass
MYSQL_ROOT_PASSWORD: root_pass
tmpfs: /var/lib/mysql
test-redis:
image: redis:alpine
CI/CD配置示例
GitHub Actions配置
# .github/workflows/integration-tests.yml
name: Integration Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_DATABASE: test_db
MYSQL_USER: test_user
MYSQL_PASSWORD: test_pass
MYSQL_ROOT_PASSWORD: root_pass
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s
redis:
image: redis:alpine
ports:
- 6379:6379
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mbstring, pdo_mysql, bcmath, xdebug
coverage: xdebug
- name: Install Dependencies
run: |
composer install --no-interaction --prefer-dist
cp .env.testing .env
- name: Setup Database
run: |
php artisan migrate --env=testing
php artisan db:seed --env=testing
- name: Run Integration Tests
run: |
php artisan test --filter=Integration
# 或使用PHPUnit
vendor/bin/phpunit --testsuite=Integration
- name: Generate Coverage Report
run: |
vendor/bin/phpunit --coverage-clover=coverage.xml
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
测试框架配置
PHPUnit配置
<!-- phpunit.xml -->
<?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="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Integration">
<directory suffix="Test.php">./tests/Integration</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
<report>
<clover outputFile="coverage.xml"/>
<html outputDirectory="coverage-html"/>
</report>
</coverage>
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="mysql"/>
<env name="DB_HOST" value="127.0.0.1"/>
<env name="DB_PORT" value="3306"/>
<env name="DB_DATABASE" value="test_db"/>
<env name="DB_USERNAME" value="test_user"/>
<env name="DB_PASSWORD" value="test_pass"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="MAIL_MAILER" value="array"/>
</php>
</phpunit>
集成测试示例
API集成测试
<?php
// tests/Integration/Api/UserApiTest.php
namespace Tests\Integration\Api;
use Tests\TestCase;
use App\Models\User;
use App\Models\Role;
use Illuminate\Foundation\Testing\RefreshDatabase;
class UserApiTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
// 创建测试数据
$this->user = User::factory()->create([
'email' => 'test@example.com',
'password' => bcrypt('password123')
]);
$this->actingAs($this->user);
}
/** @test */
public function it_can_create_user()
{
$response = $this->postJson('/api/users', [
'name' => 'New User',
'email' => 'new@example.com',
'password' => 'password123',
'role_id' => Role::factory()->create()->id
]);
$response->assertStatus(201)
->assertJsonStructure([
'data' => [
'id',
'name',
'email',
'created_at'
]
]);
$this->assertDatabaseHas('users', [
'email' => 'new@example.com'
]);
}
/** @test */
public function it_can_update_user()
{
$response = $this->putJson("/api/users/{$this->user->id}", [
'name' => 'Updated Name'
]);
$response->assertStatus(200);
$this->assertEquals('Updated Name', $this->user->fresh()->name);
}
}
数据库集成测试
<?php
// tests/Integration/Database/UserRepositoryTest.php
namespace Tests\Integration\Database;
use Tests\TestCase;
use App\Models\User;
use App\Repositories\UserRepository;
use Illuminate\Foundation\Testing\RefreshDatabase;
class UserRepositoryTest extends TestCase
{
use RefreshDatabase;
private UserRepository $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = app(UserRepository::class);
}
/** @test */
public function it_can_find_active_users()
{
// 创建测试数据
User::factory()->count(5)->create(['status' => 'active']);
User::factory()->count(3)->create(['status' => 'inactive']);
$activeUsers = $this->repository->findActiveUsers();
$this->assertCount(5, $activeUsers);
$this->assertTrue($activeUsers->every(fn($user) => $user->status === 'active'));
}
/** @test */
public function it_can_get_users_by_role()
{
$role = Role::factory()->create(['name' => 'admin']);
User::factory()->count(3)->create(['role_id' => $role->id]);
$adminUsers = $this->repository->getUsersByRole('admin');
$this->assertCount(3, $adminUsers);
}
}
外部服务集成测试
<?php
// tests/Integration/Services/PaymentServiceTest.php
namespace Tests\Integration\Services;
use Tests\TestCase;
use App\Services\PaymentService;
use App\Models\Order;
use Mockery;
class PaymentServiceTest extends TestCase
{
public function test_successful_payment_processing()
{
// 创建测试订单
$order = Order::factory()->create([
'amount' => 100.00,
'status' => 'pending'
]);
// 模拟外部支付网关
$gatewayMock = Mockery::mock('App\Contracts\PaymentGateway');
$gatewayMock->shouldReceive('charge')
->once()
->with($order->amount, $order->id)
->andReturn([
'success' => true,
'transaction_id' => 'txn_12345'
]);
$service = new PaymentService($gatewayMock);
$result = $service->processPayment($order);
// 验证结果
$this->assertTrue($result['success']);
$this->assertEquals('txn_12345', $result['transaction_id']);
// 验证订单状态变更
$this->assertEquals('paid', $order->fresh()->status);
}
public function test_failed_payment_handling()
{
// 测试支付失败的处理逻辑
}
}
测试脚本与自动化
Makefile自动化脚本
# Makefile
.PHONY: test integration-test coverage lint
test:
docker-compose -f docker-compose.test.yml run --rm test-runner vendor/bin/phpunit
integration-test:
docker-compose -f docker-compose.test.yml run --rm test-runner vendor/bin/phpunit --testsuite=Integration
coverage:
docker-compose -f docker-compose.test.yml run --rm test-runner \
vendor/bin/phpunit --coverage-html=coverage-html
lint:
vendor/bin/phpcs --standard=PSR12 app/ tests/
test-all: lint test coverage
数据库迁移与种子
<?php
// tests/CreatesApplication.php
namespace Tests;
use Illuminate\Contracts\Console\Kernel;
trait CreatesApplication
{
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
return $app;
}
}
<?php
// database/seeders/DatabaseSeeder.php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run()
{
$this->call([
RoleSeeder::class,
PermissionSeeder::class,
UserSeeder::class,
ProductSeeder::class,
]);
}
}
监控与报告
测试报告输出
<?php
// tests/Reports/TestReportGenerator.php
namespace Tests\Reports;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class TestReportGenerator
{
public function generate(array $testResults): void
{
$report = [
'timestamp' => now()->toDateTimeString(),
'total_tests' => count($testResults),
'passed' => 0,
'failed' => 0,
'skipped' => 0,
'duration' => 0,
'details' => []
];
foreach ($testResults as $result) {
$report[$result['status']]++;
$report['details'][] = $result;
}
Storage::disk('local')->put(
"test-reports/report-{$report['timestamp']}.json",
json_encode($report, JSON_PRETTY_PRINT)
);
Log::info('Test Report Generated', $report);
}
}
监控集成
<?php
// config/test-monitoring.php
return [
'slack' => [
'webhook_url' => env('SLACK_WEBHOOK_URL'),
'channel' => '#test-reports'
],
'metrics' => [
'enabled' => true,
'statsd_host' => env('STATSD_HOST', 'localhost'),
'statsd_port' => 8125
]
];
最佳实践
测试环境隔离
// .env.testing APP_ENV=testing APP_DEBUG=true DB_CONNECTION=mysql DB_HOST=test-db DB_DATABASE=test_db DB_USERNAME=test_user DB_PASSWORD=test_pass CACHE_DRIVER=array SESSION_DRIVER=array MAIL_MAILER=array QUEUE_CONNECTION=sync
并行测试配置
<!-- phpunit.xml -->
<phpunit>
<testsuites>
<testsuite name="Integration">
<directory suffix="Test.php">./tests/Integration</directory>
</testsuite>
</testsuites>
<extensions>
<extension class="Symfony\Component\Panther\ServerExtension"/>
</extensions>
<!-- 并行测试配置 -->
<php>
<ini name="memory_limit" value="512M"/>
<env name="PARALLEL_TESTING" value="true"/>
</php>
</phpunit>
测试数据管理
<?php
// tests/Traits/TestDataManager.php
namespace Tests\Traits;
trait TestDataManager
{
protected function createTestData(array $config = []): array
{
return [
'users' => User::factory()
->count($config['users_count'] ?? 10)
->create(),
'products' => Product::factory()
->count($config['products_count'] ?? 20)
->create(),
'orders' => Order::factory()
->count($config['orders_count'] ?? 30)
->create()
];
}
protected function cleanupTestData(): void
{
DB::statement('SET FOREIGN_KEY_CHECKS=0');
foreach ($this->getTestTables() as $table) {
DB::table($table)->truncate();
}
DB::statement('SET FOREIGN_KEY_CHECKS=1');
}
protected function getTestTables(): array
{
return ['users', 'products', 'orders', 'payments'];
}
}
这套方案涵盖了PHP项目集成测试自动化的完整流程,从环境搭建到测试执行,再到报告生成和监控,关键是要根据自己的项目需求进行调整,并保持测试环境的稳定性和一致性。