本文目录导读:

- 事务回滚(最常用)
- 使用PHPUnit的测试基类
- 使用数据库迁移回滚(测试环境)
- 使用工厂函数和事务回滚
- 配置测试环境数据库
- 使用内存数据库(SQLite)
- 高级用法:多表事务处理
- 自定义回滚策略
- PHPUnit配置文件示例
- 注意事项:
在ThinkPHP项目中进行数据库测试时,回滚操作是确保测试隔离性的关键,以下是几种常见的回滚策略和实现方法:
事务回滚(最常用)
<?php
namespace app\common\trait;
use think\facade\Db;
trait DatabaseTransaction
{
protected $connections = [];
protected function setUp(): void
{
parent::setUp();
// 开始事务(可以处理多个数据库连接)
$connections = [
'mysql', // 默认连接
// 'mysql2', // 其他连接
];
foreach ($connections as $connection) {
Db::connect($connection)->startTrans();
$this->connections[] = $connection;
}
}
protected function tearDown(): void
{
// 回滚事务
foreach ($this->connections as $connection) {
Db::connect($connection)->rollback();
}
parent::tearDown();
}
}
使用PHPUnit的测试基类
<?php
namespace tests;
use PHPUnit\Framework\TestCase;
use think\facade\Db;
abstract class DatabaseTestCase extends TestCase
{
protected $callback;
protected function setUp(): void
{
parent::setUp();
// 备份数据库状态
$this->beginTransaction();
}
protected function tearDown(): void
{
// 回滚数据库
$this->rollbackTransaction();
parent::tearDown();
}
protected function beginTransaction()
{
Db::startTrans();
}
protected function rollbackTransaction()
{
Db::rollback();
}
/**
* 刷新数据库(清空表数据)
*/
protected function refreshDatabase()
{
// 可选:清空特定表或重建表结构
$tables = ['users', 'orders', 'products'];
foreach ($tables as $table) {
Db::name($table)->delete(true);
}
}
}
使用数据库迁移回滚(测试环境)
<?php
namespace tests;
class MigrationTestCase extends TestCase
{
protected function setUp(): void
{
parent::setUp();
// 运行迁移
$this->artisan('migrate:fresh --seed');
// 或只运行迁移
$this->artisan('migrate');
}
protected function tearDown(): void
{
// 回滚所有迁移
$this->artisan('migrate:rollback');
parent::tearDown();
}
}
使用工厂函数和事务回滚
<?php
namespace tests\Feature;
use app\common\trait\DatabaseTransaction;
use app\models\User;
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
use DatabaseTransaction;
public function testCreateUser()
{
// 创建用户(自动事务回滚)
$user = User::create([
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => bcrypt('secret')
]);
// 断言
$this->assertDatabaseHas('users', [
'email' => 'john@example.com'
]);
// 无需手动清理,tearDown自动回滚
}
public function testUpdateUser()
{
$user = User::find(1);
$user->name = 'Jane Doe';
$user->save();
$this->assertEquals('Jane Doe', $user->name);
// 数据会在tearDown中回滚
}
}
配置测试环境数据库
// config/database.php
return [
// 测试环境配置
'connections' => [
'mysql' => [
'type' => 'mysql',
'hostname' => '127.0.0.1',
'database' => 'test_database',
'username' => 'root',
'password' => 'secret',
'prefix' => '',
// 其他配置...
],
'sqlite' => [
'type' => 'sqlite',
'database' => ':memory:',
// SQLite内存数据库,自动回滚
],
],
// 测试时默认使用
'default' => env('DB_CONNECTION', 'mysql'),
// 测试配置
'test' => [
'type' => 'mysql',
'hostname' => '127.0.0.1',
'database' => 'test_database',
'username' => 'root',
'password' => 'secret',
'prefix' => '',
],
];
使用内存数据库(SQLite)
<?php
namespace tests;
use PDO;
use PHPUnit\Framework\TestCase;
class InMemoryDatabaseTest extends TestCase
{
protected $db;
protected function setUp(): void
{
// 创建内存数据库
$this->db = new PDO('sqlite::memory:');
// 创建表结构
$this->db->exec("
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255)
)
");
}
protected function tearDown(): void
{
// 关闭连接,自动清理
$this->db = null;
}
public function testInsert()
{
$stmt = $this->db->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
$stmt->execute(['John', 'john@example.com']);
$this->assertEquals(1, $this->db->query("SELECT COUNT(*) FROM users")->fetchColumn());
}
}
高级用法:多表事务处理
<?php
namespace tests\Feature;
use think\facade\Db;
use PHPUnit\Framework\TestCase;
class AdvancedTransactionTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
// 为每个数据库连接启动事务
Db::connect('mysql')->startTrans();
Db::connect('postgresql')->startTrans();
// 可以添加Redis或其他存储的事务
// Redis::multi();
}
protected function tearDown(): void
{
// 回滚所有连接
Db::connect('mysql')->rollback();
Db::connect('postgresql')->rollback();
// 回滚Redis
// Redis::discard();
parent::tearDown();
}
public function testMultiDatabaseOperation()
{
// 在多个数据库中执行操作
Db::connect('mysql')->table('users')->insert(['name' => 'John']);
Db::connect('postgresql')->table('orders')->insert(['user_id' => 1]);
// 所有操作都会在tearDown中回滚
}
}
自定义回滚策略
<?php
namespace app\common\trait;
trait CustomRollback
{
protected $tablesToSnapshot = [];
protected function snapshotTables(array $tables)
{
$this->tablesToSnapshot = $tables;
// 保存表数据到临时表
foreach ($tables as $table) {
$this->createSnapshot($table);
}
}
protected function createSnapshot($table)
{
// 创建临时表并复制数据
$tempTable = "{$table}_snapshot";
Db::execute("CREATE TABLE {$tempTable} AS SELECT * FROM {$table}");
}
protected function restoreSnapshot()
{
foreach ($this->tablesToSnapshot as $table) {
$tempTable = "{$table}_snapshot";
// 清空原表
Db::table($table)->delete(true);
// 从临时表恢复数据
Db::execute("INSERT INTO {$table} SELECT * FROM {$tempTable}");
// 删除临时表
Db::execute("DROP TABLE {$tempTable}");
}
}
protected function tearDown(): void
{
$this->restoreSnapshot();
parent::tearDown();
}
}
PHPUnit配置文件示例
<!-- phpunit.xml -->
<phpunit bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="Feature">
<directory>./tests/Feature</directory>
</testsuite>
<testsuite name="Unit">
<directory>./tests/Unit</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="testing"/>
<env name="DB_DATABASE" value="test_database"/>
</php>
</phpunit>
注意事项:
- 不要在测试中直接提交事务:确保所有测试操作都在事务中,最后统一回滚
- 处理缓存:如果使用缓存,记得在回滚后清理缓存
- 考虑并发:在测试环境中,避免使用真实的种子数据
- 记录日志:测试过程中保留查询日志,便于调试
- 使用测试数据库:始终使用专用的测试数据库,不要在生产数据库上执行测试
通过以上方法,你可以确保每个测试都是独立且可重复的,数据库状态不会影响其他测试用例。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播