跳转到主要内容

什么是测试

测试是一种自动验证代码是否按预期运行的机制。 编写测试可以在添加或修改功能时快速确认既有行为没有被破坏。 在团队开发中,测试让你可以放心地修改和评审代码。 Laravel 从一开始就内置了对测试的支持,PestPHPUnit 都可以使用。 新安装时会自动准备好 phpunit.xml 配置和 tests/ 目录。
Pest 构建在 PHPUnit 之上,语法更简洁易读。初学者建议直接选择 Pest。

tests/ 目录结构

tests/
├── Feature/      # 功能测试
│   └── ExampleTest.php
├── Unit/         # 单元测试
│   └── ExampleTest.php
└── TestCase.php
  • Feature/:功能测试。用于包含 HTTP 请求的较大粒度测试。适合验证多对象协作、API 端点等接近整体的行为。多数测试都会写在这里。
  • Unit/:单元测试。用于单个类或方法等小粒度测试。此处不会启动 Laravel 应用,因此不能使用数据库或其他框架功能。

创建测试

使用 make:test Artisan 命令生成新的测试类。
# 生成 Feature 测试(默认)
php artisan make:test TodoTest

# 生成 Unit 测试
php artisan make:test TodoTest --unit
会生成 tests/Feature/TodoTest.php

运行测试

使用 php artisan test 命令。
php artisan test
也可以直接运行 vendor/bin/pestvendor/bin/phpunit,但 php artisan test 的输出更易读,推荐使用。 只运行特定套件:
# 只运行 Feature 测试
php artisan test --testsuite=Feature

# 遇到失败立即停止
php artisan test --stop-on-failure

基本测试写法

简单断言的示例:
<?php

test('true 应为 true', function () {
    expect(true)->toBeTrue();
});

test('字符串是否包含', function () {
    $message = 'Hello, Laravel!';

    expect($message)->toContain('Laravel');
});
<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

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

    public function test_string_contains_laravel(): void
    {
        $message = 'Hello, Laravel!';

        $this->assertStringContainsString('Laravel', $message);
    }
}

常用断言

PestPHPUnit说明
expect($x)->toBe($y)$this->assertSame($y, $x)严格相等
expect($x)->toEqual($y)$this->assertEquals($y, $x)相等(不比较类型)
expect($x)->toBeTrue()$this->assertTrue($x)true
expect($x)->toBeFalse()$this->assertFalse($x)false
expect($x)->toBeNull()$this->assertNull($x)null
expect($x)->toContain($y)$this->assertStringContainsString($y, $x)包含字符串
expect($x)->toHaveCount($n)$this->assertCount($n, $x)元素数量一致

HTTP 测试

Laravel 的 HTTP 测试无需启动真实 HTTP 服务器,就能模拟路由请求。 这是 Feature/ 中最强大的能力。

检查页面显示

get() 发送 GET 请求并校验响应。
<?php

test('显示首页', function () {
    $response = $this->get('/');

    $response->assertStatus(200);
});
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_top_page_is_displayed(): void
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}

ToDo 应用的 HTTP 测试示例

以带有 Todo 模型的应用为例,测试各 CRUD 操作。
<?php

use App\Models\Todo;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

test('ToDo 列表页可展示', function () {
    Todo::factory()->count(3)->create();

    $response = $this->get('/todos');

    $response->assertStatus(200);
    $response->assertSee('列表');
});

test('可以新增 ToDo', function () {
    $response = $this->post('/todos', [
        'title' => '学习 Laravel',
    ]);

    $response->assertRedirect('/todos');
    $this->assertDatabaseHas('todos', ['title' => '学习 Laravel']);
});

test('可以删除 ToDo', function () {
    $todo = Todo::factory()->create();

    $response = $this->delete("/todos/{$todo->id}");

    $response->assertRedirect('/todos');
    $this->assertDatabaseMissing('todos', ['id' => $todo->id]);
});
<?php

namespace Tests\Feature;

use App\Models\Todo;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class TodoTest extends TestCase
{
    use RefreshDatabase;

    public function test_todo_list_is_displayed(): void
    {
        Todo::factory()->count(3)->create();

        $response = $this->get('/todos');

        $response->assertStatus(200);
        $response->assertSee('列表');
    }

    public function test_todo_can_be_created(): void
    {
        $response = $this->post('/todos', [
            'title' => '学习 Laravel',
        ]);

        $response->assertRedirect('/todos');
        $this->assertDatabaseHas('todos', ['title' => '学习 Laravel']);
    }

    public function test_todo_can_be_deleted(): void
    {
        $todo = Todo::factory()->create();

        $response = $this->delete("/todos/{$todo->id}");

        $response->assertRedirect('/todos');
        $this->assertDatabaseMissing('todos', ['id' => $todo->id]);
    }
}

常用响应断言

方法说明
assertStatus(200)校验 HTTP 状态码
assertOk()状态码为 200
assertRedirect('/path')校验重定向目标
assertSee('文本')响应体包含指定文本
assertDontSee('文本')响应体不包含
assertJson([...])校验 JSON 数据
assertDatabaseHas('table', [...])校验数据库存在记录
assertDatabaseMissing('table', [...])校验数据库无记录
使用 RefreshDatabase trait 时,每次测试执行后都会重置数据库,避免测试间数据互相干扰。

测试环境

phpunit.xml 配置

在项目根目录的 phpunit.xml 中配置测试环境。默认 Session 和 Cache 使用 array 驱动,运行过程中不会留下数据。
<env name="APP_ENV" value="testing"/>
<env name="CACHE_STORE" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
数据库通常使用 SQLite 的内存数据库,避免产生文件:
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>

.env.testing 文件

在项目根目录创建 .env.testing 文件,测试执行时会替代 .env 被读取。 适合分离测试专用的数据库连接或外部服务配置。
APP_ENV=testing
DB_CONNECTION=sqlite
DB_DATABASE=:memory:
如果启用了配置缓存,请先运行 php artisan config:clear 再跑测试。旧缓存可能导致配置未生效。

并行测试

测试数量增多时,运行时间也会变长。 可用 --parallel 选项启动多个进程并行执行,从而缩短耗时。 首先安装 brianium/paratest
composer require brianium/paratest --dev
然后添加 --parallel
php artisan test --parallel
默认使用 CPU 核心数个进程。可用 --processes 指定:
php artisan test --parallel --processes=4
并行测试时,每个进程都需要独立的数据库。搭配 RefreshDatabase trait 与 phpunit.xml 中的内存 SQLite 就能顺利运行。

下一步

HTTP 测试

查看请求模拟、认证测试、JSON 断言等更详细的 HTTP 测试方法。
最后修改于 2026年7月13日