Laravel 单元测试与 PHPUnit

测试是确保代码质量和可靠性的关键。Laravel 内置了对 PHPUnit 的强大支持,提供了丰富的辅助方法和断言,让你能够轻松编写单元测试、功能测试和数据库测试。本章将带你从零开始,掌握在 Laravel 中进行测试的核心技能。

🧪 核心理念: 测试驱动开发(TDD)可以让你更自信地重构代码,并确保新功能不破坏原有逻辑。Laravel 的测试工具让这一过程变得简单而愉快。

1. 环境准备

Laravel 项目默认已安装 PHPUnit,配置文件为项目根目录下的 phpunit.xml。运行测试前,请确保配置了正确的数据库环境(通常使用 SQLite 内存数据库)。

创建测试数据库的 .env 设置(推荐):


DB_CONNECTION=sqlite
DB_DATABASE=:memory:
                    

运行所有测试:


php artisan test
                    

或者使用 PHPUnit 直接运行:


./vendor/bin/phpunit
                    

2. 创建测试用例

使用 Artisan 命令生成测试类:


php artisan make:test UserTest
php artisan make:test UserTest --unit  # 生成单元测试(不启动 Laravel 应用)
                    

生成的测试类位于 tests/Feature(功能测试)或 tests/Unit(单元测试)。功能测试会启动完整的 Laravel 应用,适合测试路由、控制器等。

示例:一个简单的单元测试


<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase
{
    public function test_basic_test()
    {
        $this->assertTrue(true);
    }
}
                    

3. 常用断言方法

PHPUnit 提供了丰富的断言,Laravel 还扩展了一些针对数据库、HTTP 的断言。以下列出常用断言:

高层
断言方法说明
assertTrue($condition)断言条件为真
assertFalse($condition)断言条件为假
assertEquals($expected, $actual)断言两个值相等
assertSame($expected, $actual)断言两个值严格相等(类型也相同)
assertNull($value)断言值为 null
assertNotNull($value)断言值不为 null
assertCount($expectedCount, $haystack)断言数组/集合元素数量
assertInstanceOf($expected, $actual)断言对象为指定类的实例
assertThrows(callable $callback, string $exceptionClass)断言抛出异常(Laravel 辅助)

4. 数据库测试

Laravel 提供了数据库测试的便捷方法,可以在测试中使用 RefreshDatabase trait 来重置数据库,避免数据污染。


use Illuminate\Foundation\Testing\RefreshDatabase;

class UserTest extends TestCase
{
    use RefreshDatabase;

    public function test_user_can_be_created()
    {
        $user = User::factory()->create([
            'email' => 'test@example.com',
        ]);

        $this->assertDatabaseHas('users', [
            'email' => 'test@example.com',
        ]);
    }
}
                    

常用数据库断言:


$this->assertDatabaseHas('users', ['email' => 'test@example.com']);
$this->assertDatabaseMissing('users', ['email' => 'notexist@example.com']);
$this->assertDatabaseCount('users', 1);
                    

5. HTTP 测试(功能测试)

功能测试可以模拟 HTTP 请求并验证响应。

示例:测试用户注册接口


public function test_user_can_register()
{
    $response = $this->post('/register', [
        'name' => 'John Doe',
        'email' => 'john@example.com',
        'password' => 'password',
        'password_confirmation' => 'password',
    ]);

    $response->assertStatus(302); // 重定向
    $this->assertDatabaseHas('users', ['email' => 'john@example.com']);
}
                    

常用 HTTP 断言:


$response->assertStatus(200);
$response->assertOk();
$response->assertViewIs('welcome');          // 断言视图
$response->assertSee('Hello, World');        // 断言响应包含文本
$response->assertJson(['status' => 'success']); // 断言 JSON 响应
$response->assertRedirect('/home');
                    

6. 模拟对象(Mocking)

使用 Laravel 的 mock() 辅助函数或 PHPUnit 的 Mock 功能,可以模拟依赖,隔离测试。


// 模拟邮件发送
Mail::fake();

// 执行注册逻辑
$response = $this->post('/register', [...]);

// 断言邮件已发送
Mail::assertSent(WelcomeMail::class);

// 模拟外部服务
Http::fake([
    'api.example.com/*' => Http::response(['data' => 'mocked'], 200),
]);
                    

Laravel 为常用的服务提供了 fake 方法:Event::fake()Queue::fake()Notification::fake() 等。

7. 测试辅助方法

Laravel 的测试基类(Tests\TestCase)提供了许多实用方法,如:


$this->actingAs($user);      // 模拟已认证用户
$this->withoutExceptionHandling(); // 关闭异常处理(便于调试)
$this->seed();               // 填充数据库种子
$this->artisan('cache:clear'); // 执行 Artisan 命令
                    

8. 使用模型工厂生成测试数据

模型工厂(database/factories)可以快速生成测试数据。


// 创建单个用户
$user = User::factory()->create();

// 创建多个
$users = User::factory()->count(5)->create();

// 定义关联
$user = User::factory()
    ->has(Post::factory()->count(3))
    ->create();
                    

9. 最佳实践与常见陷阱

  • 使用 RefreshDatabase trait: 确保每个测试用例运行在干净的数据库上。
  • 隔离测试: 每个测试应独立运行,不依赖其他测试的结果。
  • 命名清晰: 测试方法名应描述其行为,如 test_user_cannot_register_with_invalid_email
  • 避免测试环境使用实际外部服务: 使用 Http::fake() 或模拟对象。
  • 保持测试快速: 使用内存数据库(SQLite)和避免不必要的文件操作。
  • 关注核心逻辑: 不要测试框架本身,而是测试你的业务逻辑。
💡 提示: 使用 php artisan test --coverage 可生成代码覆盖率报告(需安装 pcovxdebug)。

10. 总结

Laravel 与 PHPUnit 的完美结合,让你能够轻松编写高质量的测试用例。从简单的单元测试到复杂的 HTTP 测试和数据库测试,Laravel 提供了丰富的工具和便捷的 API。养成编写测试的习惯,将使你的代码更加健壮,维护成本大幅降低。

📖 官方文档: Laravel Testing 提供了更全面的指南。