本文目录导读:

在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和配置,可以让你的测试套件既高效又稳定。