ThinkPHP项目单元测试怎样编写

wen PHP项目 3

本文目录导读:

ThinkPHP项目单元测试怎样编写

  1. 环境准备
  2. 创建测试基类
  3. 单元测试示例
  4. 数据库测试辅助方法
  5. 运行测试
  6. 测试配置建议
  7. 最佳实践建议

在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,
    ],
];

最佳实践建议

  1. 数据隔离:每个测试前创建测试数据,测试后清理
  2. 命名规范:测试方法用test开头或使用@test注解
  3. 单一职责:每个测试方法只测试一个功能点
  4. 使用数据提供器:对于多组测试数据,使用@dataProvider
  5. 断言明确:使用明确的断言信息,便于调试
  6. 避免硬编码:尽量减少测试中的硬编码值
  7. 测试速度:优先使用内存数据库(如SQLite)

这样完整的测试体系能够确保你的ThinkPHP项目核心功能的质量,为后续重构和维护提供安全保障。

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