PHP项目Laravel数据库测试事务回滚

wen PHP项目 5

本文目录导读:

PHP项目Laravel数据库测试事务回滚

  1. 基础设置
  2. 配置测试数据库
  3. 高级用法
  4. 性能优化
  5. 常见问题和解决方案
  6. 最佳实践
  7. 监控和调试

在Laravel中进行数据库测试时,事务回滚是一种确保测试数据不会污染实际数据库的常用方法,以下是详细的实现方案:

基础设置

使用RefreshDatabase Trait(推荐)

<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserTest extends TestCase
{
    use RefreshDatabase;
    public function test_create_user()
    {
        $response = $this->post('/users', [
            'name' => 'John Doe',
            'email' => 'john@example.com',
            'password' => 'password123'
        ]);
        $response->assertStatus(201);
        $this->assertDatabaseHas('users', [
            'email' => 'john@example.com'
        ]);
    }
}

使用DatabaseTransactions Trait(轻量级)

<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class UserTest extends TestCase
{
    use DatabaseTransactions;
    public function test_update_user()
    {
        $user = User::factory()->create();
        $response = $this->put("/users/{$user->id}", [
            'name' => 'Updated Name'
        ]);
        $response->assertStatus(200);
        $this->assertDatabaseHas('users', [
            'id' => $user->id,
            'name' => 'Updated Name'
        ]);
    }
}

配置测试数据库

.env.testing 文件

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_test
DB_USERNAME=root
DB_PASSWORD=

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="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="BCRYPT_ROUNDS" value="4"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="DB_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
        <env name="MAIL_MAILER" value="array"/>
        <env name="QUEUE_CONNECTION" value="sync"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="TELESCOPE_ENABLED" value="false"/>
    </php>
</phpunit>

高级用法

手动事务控制

<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\DB;
class AdvancedTest extends BaseTestCase
{
    public function test_with_manual_transaction()
    {
        DB::beginTransaction();
        try {
            // 测试代码
            $user = User::create([
                'name' => 'Test User',
                'email' => 'test@example.com',
                'password' => bcrypt('password')
            ]);
            $this->assertNotNull($user->id);
            // 在事务内验证
            $this->assertEquals(1, DB::table('users')->count());
            DB::rollBack();
        } catch (\Exception $e) {
            DB::rollBack();
            throw $e;
        }
        // 回滚后验证
        $this->assertEquals(0, DB::table('users')->count());
    }
}

跨多个数据库的事务处理

<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class MultiDatabaseTest extends TestCase
{
    use DatabaseTransactions;
    protected $connectionsToTransact = ['mysql', 'mysql_secondary'];
    public function test_multi_database_operation()
    {
        DB::connection('mysql')->table('users')->insert([
            'name' => 'User 1',
            'email' => 'user1@example.com'
        ]);
        DB::connection('mysql_secondary')->table('logs')->insert([
            'message' => 'User created',
            'user_id' => 1
        ]);
        // 事务结束后,两个数据库的更改都会被回滚
    }
}

性能优化

使用连接池(MySQL)

// config/database.php
'mysql' => [
    'driver' => 'mysql',
    'host' => env('DB_HOST', '127.0.0.1'),
    'database' => env('DB_DATABASE', 'forge'),
    'username' => env('DB_USERNAME', 'forge'),
    'password' => env('DB_PASSWORD', ''),
    'pool' => [
        'max' => 10,
        'min' => 2,
    ],
],

测试数据工厂优化

// database/factories/UserFactory.php
$factory->define(User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => bcrypt('password'),
        'remember_token' => Str::random(10),
    ];
});

常见问题和解决方案

问题1:内存数据库(SQLite)不生效

// 确保 SQLite 扩展已启用
// 检查 php.ini 中的 pdo_sqlite 扩展

问题2:外键约束导致的事务失败

// 在测试基类中禁用外键约束
class TestCase extends BaseTestCase
{
    use CreatesApplication;
    protected function setUp(): void
    {
        parent::setUp();
        if (DB::connection()->getDriverName() === 'mysql') {
            DB::statement('SET FOREIGN_KEY_CHECKS = 0');
        }
    }
    protected function tearDown(): void
    {
        if (DB::connection()->getDriverName() === 'mysql') {
            DB::statement('SET FOREIGN_KEY_CHECKS = 1');
        }
        parent::tearDown();
    }
}

问题3:测试并行执行时的冲突

// parallel testing 配置(需要安装 paratest)
// composer require brianium/paratest
// vendor/bin/paratest --processes 8

最佳实践

选择合适的事务策略

// 适用于简单测试
use DatabaseTransactions;
// 适用于需要重置所有数据的测试
use RefreshDatabase;
// 适用于大型测试套件(混合使用)
class UserTest extends TestCase
{
    use DatabaseTransactions; // 快速回滚
    public function test_complex_scenario()
    {
        // 使用 RefreshDatabase 风格的重置
        $this->refreshDatabase();
        // 测试代码
    }
}

测试辅助方法

// tests/TestCase.php
abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;
    protected function setUp(): void
    {
        parent::setUp();
        // 设置测试环境
        config(['app.debug' => true]);
    }
    protected function tearDown(): void
    {
        $this->flushSession();
        parent::tearDown();
    }
}

监控和调试

查看执行的SQL语句

// 在测试中启用 SQL 日志
DB::enableQueryLog();
$this->post('/users', [...]);
dump(DB::getQueryLog());

通过合理使用事务回滚机制,可以确保测试的隔离性、可靠性和性能,选择合适的Trait和配置,可以让你的测试套件既高效又稳定。

抱歉,评论功能暂时关闭!