本文目录导读:

在ThinkPHP项目中进行单元测试,我推荐使用PHPUnit,以下是完整的编写指南:
环境准备
安装PHPUnit
composer require phpunit/phpunit --dev
创建phpunit.xml配置文件
在项目根目录创建phpunit.xml:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Application Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
</phpunit>
创建测试基类
<?php
// tests/TestCase.php
namespace tests;
use think\App;
use think\facade\Db;
use PHPUnit\Framework\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
protected $app;
protected $db;
protected function setUp(): void
{
parent::setUp();
// 初始化应用
$this->app = new App();
$this->app->initialize();
// 使用测试数据库配置
$this->app->config->set([
'type' => 'mysql',
'hostname' => 'localhost',
'database' => 'test_db',
'username' => 'root',
'password' => 'password',
'hostport' => '3306',
], 'database.connections');
// 启动数据库
$this->db = Db::connect('mysql');
}
protected function tearDown(): void
{
// 清理测试数据
$this->db->execute('SET FOREIGN_KEY_CHECKS=0');
foreach ($this->getTables() as $table) {
$this->db->execute("TRUNCATE TABLE `{$table}`");
}
$this->db->execute('SET FOREIGN_KEY_CHECKS=1');
parent::tearDown();
}
protected function getTables()
{
$tables = $this->db->query('SHOW TABLES');
return array_map('reset', $tables);
}
// 辅助方法:创建测试数据
protected function createTestData($model, $data = [])
{
$defaultData = [
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
];
return $model::create(array_merge($defaultData, $data));
}
}
单元测试示例
模型的单元测试
<?php
// tests/Unit/UserModelTest.php
namespace tests\Unit;
use app\model\User;
use tests\TestCase;
class UserModelTest extends TestCase
{
/**
* 测试创建用户
*/
public function testCreateUser()
{
$user = User::create([
'username' => 'test_user',
'email' => 'test@example.com',
'password' => password_hash('123456', PASSWORD_DEFAULT),
'status' => 1
]);
$this->assertInstanceOf(User::class, $user);
$this->assertEquals('test_user', $user->username);
$this->assertDatabaseHas('users', ['username' => 'test_user']);
}
/**
* 测试查询用户
*/
public function testFindUser()
{
$user = $this->createTestData(User::class, [
'username' => 'find_user',
'email' => 'find@example.com'
]);
$found = User::find($user->id);
$this->assertEquals('find_user', $found->username);
}
/**
* 测试更新用户
*/
public function testUpdateUser()
{
$user = $this->createTestData(User::class, [
'username' => 'update_user'
]);
$user->username = 'updated_user';
$user->save();
$this->assertEquals('updated_user', User::find($user->id)->username);
}
/**
* 测试删除用户
*/
public function testDeleteUser()
{
$user = $this->createTestData(User::class);
$userId = $user->id;
$user->delete();
$this->assertNull(User::find($userId));
}
/**
* 测试数据验证
*/
public function testUserValidation()
{
$this->expectException(\think\exception\ValidateException::class);
$user = new User([
'username' => '', // 空用户名
'email' => 'invalid-email' // 无效邮箱
]);
$user->save();
}
}
服务类的单元测试
<?php
// tests/Unit/UserServiceTest.php
namespace tests\Unit;
use app\service\UserService;
use tests\TestCase;
use app\model\User;
class UserServiceTest extends TestCase
{
protected $userService;
protected function setUp(): void
{
parent::setUp();
$this->userService = new UserService();
}
/**
* @test
* @dataProvider userDataProvider
*/
public function testCreateUser($data, $expected)
{
$result = $this->userService->createUser($data);
$this->assertEquals($expected, $result['success']);
if ($expected) {
$this->assertDatabaseHas('users', ['username' => $data['username']]);
}
}
/**
* 数据提供器
*/
public function userDataProvider()
{
return [
'有效数据' => [
[
'username' => 'valid_user',
'email' => 'valid@example.com',
'password' => 'password123'
],
true
],
'无效数据' => [
[
'username' => '',
'email' => 'invalid',
'password' => 'short'
],
false
]
];
}
/**
* 测试登录逻辑
*/
public function testLogin()
{
// 创建测试用户
$user = User::create([
'username' => 'login_user',
'email' => 'login@example.com',
'password' => password_hash('pass123', PASSWORD_DEFAULT),
'status' => 1
]);
// 正确密码
$result = $this->userService->login('login_user', 'pass123');
$this->assertTrue($result['success']);
// 错误密码
$result = $this->userService->login('login_user', 'wrongpassword');
$this->assertFalse($result['success']);
}
}
控制器的单元测试
<?php
// tests/Feature/UserControllerTest.php
namespace tests\Feature;
use tests\TestCase;
use app\model\User;
class UserControllerTest extends TestCase
{
/**
* 测试用户列表接口
*/
public function testUserList()
{
// 创建测试数据
$this->createTestData(User::class, ['username' => 'user1']);
$this->createTestData(User::class, ['username' => 'user2']);
// 模拟请求
$request = $this->app->request
->withMethod('GET')
->withHeader('X-Requested-With', 'XMLHttpRequest');
$response = $this->app->http->run($request);
$this->assertEquals(200, $response->getCode());
$data = json_decode($response->getContent(), true);
$this->assertCount(2, $data['data']);
}
/**
* 测试用户详情接口
*/
public function testUserDetail()
{
$user = $this->createTestData(User::class, [
'username' => 'detail_user'
]);
$request = $this->app->request
->withMethod('GET')
->withRoute('/api/users/' . $user->id);
$response = $this->app->http->run($request);
$this->assertEquals(200, $response->getCode());
$this->assertStringContainsString('detail_user', $response->getContent());
}
}
数据库测试辅助方法
<?php
// tests/Traits/DatabaseAssertions.php
namespace tests\Traits;
use think\facade\Db;
trait DatabaseAssertions
{
/**
* 断言数据库中存在记录
*/
protected function assertDatabaseHas($table, $conditions)
{
$count = Db::name($table)->where($conditions)->count();
$this->assertGreaterThan(0, $count, sprintf(
'表 %s 中未找到满足条件的数据: %s',
$table,
json_encode($conditions)
));
}
/**
* 断言数据库中不存在记录
*/
protected function assertDatabaseMissing($table, $conditions)
{
$count = Db::name($table)->where($conditions)->count();
$this->assertEquals(0, $count, sprintf(
'表 %s 中找到不期望的数据: %s',
$table,
json_encode($conditions)
));
}
/**
* 断言记录数量
*/
protected function assertDatabaseCount($table, $expectedCount)
{
$actualCount = Db::name($table)->count();
$this->assertEquals($expectedCount, $actualCount, sprintf(
'表 %s 的记录数量不符合预期,期望: %d, 实际: %d',
$table,
$expectedCount,
$actualCount
));
}
}
运行测试
运行所有测试
php vendor/bin/phpunit
运行指定测试文件
php vendor/bin/phpunit tests/Unit/UserModelTest.php
运行指定测试方法
php vendor/bin/phpunit --filter testCreateUser tests/Unit/UserModelTest.php
查看测试覆盖率
php vendor/bin/phpunit --coverage-html coverage
测试配置建议
// config/test.php 测试配置文件
return [
// 测试数据库配置
'database' => [
'type' => 'mysql',
'hostname' => 'localhost',
'database' => 'test_db',
'username' => 'root',
'password' => '',
'hostport' => '3306',
'charset' => 'utf8mb4',
'prefix' => '',
],
// 是否开启调试模式
'debug' => true,
// 测试环境不发送邮件等
'mail' => [
'enabled' => false,
],
];
最佳实践建议
- 数据隔离:每个测试前创建测试数据,测试后清理
- 命名规范:测试方法用
test开头或使用@test注解 - 单一职责:每个测试方法只测试一个功能点
- 使用数据提供器:对于多组测试数据,使用
@dataProvider - 断言明确:使用明确的断言信息,便于调试
- 避免硬编码:尽量减少测试中的硬编码值
- 测试速度:优先使用内存数据库(如SQLite)
这样完整的测试体系能够确保你的ThinkPHP项目核心功能的质量,为后续重构和维护提供安全保障。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播